2009-11-10 16:13:06

by John Johansen

[permalink] [raw]
Subject: [AppArmor #3 0/12] AppArmor security module

This is the newest version of the AppArmor security module it has been
rewritten to use the security_path hooks instead of the previous vfs
approach. The current implementation is aimed at being as semantically
close to previous versions of AppArmor as possible while using the
existing LSM infrastructure.

The rewrite is functional and roughly equivalent to previous versions
of AppArmor based off of vfs patching. Development is on going and
improvements to file, capability, network, resource usage and ipc mediation
are planned.

_Issues NOT currently addressed and will be address in the next post_
* AppArmor audit framework has not yet been updated as suggested by
Eric Paris in
http://marc.info/?l=linux-security-module&m=125778105017307&w=2
* AppArmor mmap_min_addr is broken and needs to be fixed as pointed out by
Eric Paris in
http://marc.info/?l=linux-security-module&m=125778004815241&w=2

_Issues Addressed Since Last Time AppArmor was Posted_
* Implemented change recommended by Tetsuo Handa in feedback:
http://marc.info/?l=linux-security-module&m=125730973023168&w=2
http://marc.info/?l=linux-security-module&m=125740018700307&w=2
- removed read head reset in policy_unpack
- added addition comments on locking, refcounting, and memory allocation
- reworked ref counting some so that more references are held explicitly
- drop dead/unreachable code
- fix oops in putting caps cache cpu_local var
- fix refcounting bug causing leak of creds
- reworked __d_path race detection and removal of (deleted) string
* fixed bug in nameresolution failure in apparmor_bprm_set_creds that could
cause a null pointer dereference oops
* fix bug in removal of child profiles that would lead to null pointer
dereference oops. Cleaned up code and removed dead portions
* rework filter and newest profile cleaning them up after changes made for
above removal bug.
* Cleanup namespace code, removing unused fns and adding addition comments
* move profile load/replace/remove routines from policy_unpack.c to policy.c
this allowed cleaning up the interface, allowing for more core policy
functions to be static, and also allows policy_unpack to only contain
unpack code.


AppArmor documentation can currently be found at
http://developer.novell.com/wiki/index.php/Apparmor

The unflattened AppArmor git tree can be found at
git://kernel.ubuntu.com/jj/apparmor-mainline.git


The AppArmor project is currently in transition and will be moving
away from Novell forge. The current upstream for the AppArmor tools
can be found at
https://launchpad.net/apparmor

The final location of the documentation and mailing lists have
not been determined and will be updated when known.


2009-11-10 16:13:14

by John Johansen

[permalink] [raw]
Subject: [PATCH 01/12] AppArmor: misc. base functions and defines

Miscellaneous functions and defines needed by AppArmor, including
the base path resolution routines.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/apparmor.h | 61 +++++++++++
security/apparmor/include/path.h | 21 ++++
security/apparmor/lib.c | 76 +++++++++++++
security/apparmor/path.c | 196 ++++++++++++++++++++++++++++++++++
4 files changed, 354 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/apparmor.h
create mode 100644 security/apparmor/include/path.h
create mode 100644 security/apparmor/lib.c
create mode 100644 security/apparmor/path.c

diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
new file mode 100644
index 0000000..1bf700f
--- /dev/null
+++ b/security/apparmor/include/apparmor.h
@@ -0,0 +1,61 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor basic global and lib definitions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __APPARMOR_H
+#define __APPARMOR_H
+
+#include <linux/fs.h>
+
+/* Control parameters settable thru module/boot flags or
+ * via /sys/kernel/security/apparmor/control */
+extern enum audit_mode aa_g_audit;
+extern int aa_g_audit_header;
+extern int aa_g_debug;
+extern int aa_g_lock_policy;
+extern int aa_g_logsyscall;
+extern unsigned int aa_g_path_max;
+
+/*
+ * 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 (aa_g_debug && printk_ratelimit()) \
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
+ } while (0)
+
+#define AA_ERROR(fmt, args...) \
+ do { \
+ if (printk_ratelimit()) \
+ printk(KERN_ERR "AppArmor: " fmt, ##args); \
+ } while (0)
+
+/* Flag indicating whether initialization completed */
+extern int apparmor_initialized;
+void apparmor_disable(void);
+
+/* fn's in lib */
+char *aa_split_name_from_ns(char *args, char **ns_name);
+int aa_strneq(const char *str, const char *sub, int len);
+char *aa_strchrnul(const char *s, int c);
+void aa_info_message(const char *str);
+
+static inline int mediated_filesystem(struct inode *inode)
+{
+ return !(inode->i_sb->s_flags & MS_NOUSER);
+}
+
+#endif /* __APPARMOR_H */
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
new file mode 100644
index 0000000..f146868
--- /dev/null
+++ b/security/apparmor/include/path.h
@@ -0,0 +1,21 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor basic path manipulation function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_PATH_H
+#define __AA_PATH_H
+
+int aa_get_name(struct path *path, int is_dir, char **buffer, char **name);
+char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen);
+
+#endif /* __AA_PATH_H */
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
new file mode 100644
index 0000000..739705c
--- /dev/null
+++ b/security/apparmor/lib.c
@@ -0,0 +1,76 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains basic common functions used in AppArmor
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "include/audit.h"
+
+char *aa_strchrnul(const char *s, int c)
+{
+ for (; *s != (char)c && *s != '\0'; ++s)
+ ;
+ return (char *)s;
+}
+
+char *aa_split_name_from_ns(char *args, char **ns_name)
+{
+ char *name = strstrip(args);
+
+ *ns_name = NULL;
+ if (args[0] == ':') {
+ char *split = strstrip(strchr(&args[1], ':'));
+
+ if (!split)
+ return NULL;
+
+ *split = 0;
+ *ns_name = &args[1];
+ name = strstrip(split + 1);
+ }
+ if (*name == 0)
+ name = NULL;
+
+ return name;
+}
+
+/**
+ * aa_strneq - compare null terminated @str to a non null terminated substring
+ * @str: a null terminated string
+ * @sub: a substring, not necessarily null terminated
+ * @len: length of @sub to compare
+ *
+ * The @str string must be full consumed for this to be considered a match
+ */
+int aa_strneq(const char *str, const char *sub, int len)
+{
+ int res = strncmp(str, sub, len);
+ if (res)
+ return 0;
+ if (str[len] == 0)
+ return 1;
+ return 0;
+}
+
+void aa_info_message(const char *str)
+{
+ struct aa_audit sa = {
+ .gfp_mask = GFP_KERNEL,
+ .info = str,
+ };
+ printk(KERN_INFO "AppArmor: %s\n", str);
+ if (audit_enabled)
+ aa_audit(AUDIT_APPARMOR_STATUS, NULL, &sa, NULL);
+}
+
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
new file mode 100644
index 0000000..c816c79
--- /dev/null
+++ b/security/apparmor/path.c
@@ -0,0 +1,196 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor function for pathnames
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/mnt_namespace.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs_struct.h>
+
+#include "include/apparmor.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+/**
+ * d_namespace_path - lookup a name associated with a given path
+ * @path: path to lookup
+ * @buf: buffer to store path to
+ * @buflen: length of @buf
+ * @name: returns pointer for start of path name with in @buf
+ * @flags: flags controling path lookup
+ *
+ */
+static int d_namespace_path(struct path *path, char *buf, int buflen,
+ char **name, int flags)
+{
+ struct path root, tmp, ns_root = { };
+ char *res;
+ int deleted;
+ int error = 0;
+
+ read_lock(&current->fs->lock);
+ root = current->fs->root;
+ /* released below */
+ path_get(&current->fs->root);
+ read_unlock(&current->fs->lock);
+ spin_lock(&vfsmount_lock);
+ if (root.mnt && root.mnt->mnt_ns)
+ /* released below */
+ ns_root.mnt = mntget(root.mnt->mnt_ns->root);
+ if (ns_root.mnt)
+ /* released below */
+ ns_root.dentry = dget(ns_root.mnt->mnt_root);
+ spin_unlock(&vfsmount_lock);
+ spin_lock(&dcache_lock);
+
+ /* There is a race window between path lookup here and the
+ * need to strip the " (deleted) string that __d_path applies
+ * Detect the race and relookup the path
+ */
+ do {
+ tmp = ns_root;
+ deleted = d_unlinked(path->dentry);
+ res = __d_path(path, &tmp, buf, buflen);
+
+ } while (deleted != d_unlinked(path->dentry));
+
+ *name = res;
+ /* handle error conditions - and still allow a partial path to
+ * be returned.
+ */
+ if (IS_ERR(res)) {
+ error = PTR_ERR(res);
+ *name = buf;
+ } else if (deleted) {
+ /* The stripping of (deleted) is a hack that could be removed
+ * with an updated __d_path
+ */
+
+ if (!path->dentry->d_inode || flags & PFLAG_DELETED_NAMES)
+ /* On some filesystems, newly allocated dentries appear
+ * to the security_path hooks as a deleted
+ * dentry except without an inode allocated.
+ *
+ * Remove the appended deleted text and return as a
+ * string for normal mediation. The (deleted) string
+ * is guarenteed to be added in this case, so just
+ * strip it.
+ */
+ buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */
+ else
+ error = -ENOENT;
+ } else if (flags & ~PFLAG_CONNECT_PATH &&
+ tmp.dentry != ns_root.dentry && tmp.mnt != ns_root.mnt) {
+ /* disconnected path, don't return pathname starting with '/' */
+ error = -ESTALE;
+ if (*res == '/')
+ *name = res + 1;
+ }
+
+ spin_unlock(&dcache_lock);
+ path_put(&root);
+ path_put(&ns_root);
+
+ return error;
+}
+
+static int get_name_to_buffer(struct path *path, int is_dir, char *buffer,
+ int size, char **name, int flags)
+{
+ int error = d_namespace_path(path, buffer, size - is_dir, name, flags);
+
+ if (!error && is_dir && (*name)[1] != '\0')
+ /*
+ * Append "/" to the pathname. The root directory is a special
+ * case; it already ends in slash.
+ */
+ strcpy(&buffer[size - 2], "/");
+
+ return error;
+}
+
+/**
+ * aa_get_name - compute the pathname of a file
+ * @path: path the file
+ * @is_dir: set if the file is a directory
+ * @buffer: buffer that aa_get_name() allocated
+ * @name: the error code indicating whether aa_get_name failed
+ *
+ * Returns an error code if the there was a failure in obtaining the
+ * name.
+ *
+ * @name is apointer to the beginning of the pathname (which usually differs
+ * from the beginning of the buffer), or NULL. If there is an error @name
+ * may contain a partial or invalid name (in the case of a deleted file), that
+ * can be used for audit purposes, but it can not be used for mediation.
+ *
+ * We need @is_dir to indicate whether the file is a directory or not because
+ * the file may not yet exist, and so we cannot check the inode's file type.
+ */
+int aa_get_name(struct path *path, int is_dir, char **buffer, char **name)
+{
+ char *buf, *str = NULL;
+ int size = 256;
+ int error;
+
+ *name = NULL;
+ *buffer = NULL;
+ for (;;) {
+ /* freed by caller */
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ error = get_name_to_buffer(path, is_dir, buf, size, &str, 0);
+ if (!error || (error == -ENOENT) || (error == -ESTALE))
+ break;
+
+ kfree(buf);
+ size <<= 1;
+ if (size > aa_g_path_max)
+ return -ENAMETOOLONG;
+ }
+ *buffer = buf;
+ *name = str;
+
+ return error;
+}
+
+char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen)
+{
+ if (buflen < 1)
+ return NULL;
+ buffer += --buflen;
+ *buffer = '\0';
+
+ while (table) {
+ int namelen = strlen(table->procname);
+
+ if (buflen < namelen + 1)
+ return NULL;
+ buflen -= namelen + 1;
+ buffer -= namelen;
+ memcpy(buffer, table->procname, namelen);
+ *--buffer = '/';
+ table = table->parent;
+ }
+ if (buflen < 4)
+ return NULL;
+ buffer -= 4;
+ memcpy(buffer, "/sys", 4);
+
+ return buffer;
+}
--
1.6.3.3

2009-11-10 16:13:18

by John Johansen

[permalink] [raw]
Subject: [PATCH 02/12] AppArmor: basic auditing infrastructure.

Update kenel audit range comments to show AppArmor's registered range of
1500-1599. This range used to be reserved for LSPP but LSPP uses the
SELinux range and the range was given to AppArmor.
Patch is not in mainline -- pending AppArmor code submission to lkml

Add the core routine for AppArmor auditing.

Signed-off-by: John Johansen <[email protected]>
---
include/linux/audit.h | 10 ++-
security/apparmor/audit.c | 161 +++++++++++++++++++++++++++++++++++++
security/apparmor/include/audit.h | 57 +++++++++++++
3 files changed, 227 insertions(+), 1 deletions(-)
create mode 100644 security/apparmor/audit.c
create mode 100644 security/apparmor/include/audit.h

diff --git a/include/linux/audit.h b/include/linux/audit.h
index 3c7a358..a1db25b 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -33,7 +33,7 @@
* 1200 - 1299 messages internal to the audit daemon
* 1300 - 1399 audit event messages
* 1400 - 1499 SE Linux use
- * 1500 - 1599 kernel LSPP events
+ * 1500 - 1599 AppArmor use
* 1600 - 1699 kernel crypto events
* 1700 - 1799 kernel anomaly records
* 1800 - 1899 kernel integrity events
@@ -122,6 +122,14 @@
#define AUDIT_MAC_UNLBL_STCADD 1416 /* NetLabel: add a static label */
#define AUDIT_MAC_UNLBL_STCDEL 1417 /* NetLabel: del a static label */

+#define AUDIT_APPARMOR_AUDIT 1501 /* AppArmor audited grants */
+#define AUDIT_APPARMOR_ALLOWED 1502 /* Allowed Access for learning */
+#define AUDIT_APPARMOR_DENIED 1503
+#define AUDIT_APPARMOR_HINT 1504 /* Process Tracking information */
+#define AUDIT_APPARMOR_STATUS 1505 /* Changes in config */
+#define AUDIT_APPARMOR_ERROR 1506 /* Internal AppArmor Errors */
+#define AUDIT_APPARMOR_KILL 1507 /* AppArmor killing processes */
+
#define AUDIT_FIRST_KERN_ANOM_MSG 1700
#define AUDIT_LAST_KERN_ANOM_MSG 1799
#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c
new file mode 100644
index 0000000..54f4dc3
--- /dev/null
+++ b/security/apparmor/audit.c
@@ -0,0 +1,161 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor auditing functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/audit.h>
+#include <linux/socket.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/policy.h"
+
+const char *audit_mode_names[] = {
+ "normal",
+ "quiet_denied",
+ "quiet",
+ "noquiet",
+ "all"
+};
+
+static char *aa_audit_type[] = {
+ "APPARMOR_AUDIT",
+ "APPARMOR_ALLOWED",
+ "APPARMOR_DENIED",
+ "APPARMOR_HINT",
+ "APPARMOR_STATUS",
+ "APPARMOR_ERROR",
+ "APPARMOR_KILLED"
+};
+
+/*
+ * Currently AppArmor auditing is fed straight into the audit framework.
+ *
+ * TODO:
+ * netlink interface for complain mode
+ * user auditing, - send user auditing to netlink interface
+ * system control of whether user audit messages go to system log
+ */
+
+static int aa_audit_base(int type, struct aa_profile *profile,
+ struct aa_audit *sa, struct audit_context *audit_cxt,
+ void (*cb) (struct audit_buffer *, void *))
+{
+ struct audit_buffer *ab = NULL;
+ struct task_struct *task = sa->task ? sa->task : current;
+
+ if (profile && PROFILE_KILL(profile) && type == AUDIT_APPARMOR_DENIED)
+ type = AUDIT_APPARMOR_KILL;
+
+ /* ab freed below in audit_log_end */
+ ab = audit_log_start(audit_cxt, sa->gfp_mask, type);
+
+ if (!ab) {
+ AA_ERROR("(%d) Unable to log event of type (%d)\n",
+ -ENOMEM, type);
+ sa->error = -ENOMEM;
+ goto out;
+ }
+
+ if (aa_g_audit_header)
+ audit_log_format(ab, "type=%s ",
+ aa_audit_type[type - AUDIT_APPARMOR_AUDIT]);
+
+ if (sa->operation)
+ audit_log_format(ab, "operation=\"%s\"", sa->operation);
+
+ if (sa->info) {
+ audit_log_format(ab, " info=\"%s\"", sa->info);
+ if (sa->error)
+ audit_log_format(ab, " error=%d", sa->error);
+ }
+
+ audit_log_format(ab, " pid=%d", task->pid);
+
+ if (profile) {
+ pid_t pid = task->real_parent->pid;
+ audit_log_format(ab, " parent=%d", pid);
+ audit_log_format(ab, " profile=");
+ audit_log_untrustedstring(ab, profile->fqname);
+
+ if (profile->ns != default_namespace) {
+ audit_log_format(ab, " namespace=");
+ audit_log_untrustedstring(ab, profile->ns->base.name);
+ }
+ }
+
+ if (cb)
+ cb(ab, sa);
+
+ audit_log_end(ab);
+
+out:
+ if (type == AUDIT_APPARMOR_KILL)
+ (void)send_sig_info(SIGKILL, NULL, task);
+
+ return type == AUDIT_APPARMOR_ALLOWED ? 0 : sa->error;
+}
+
+/**
+ * aa_audit - Log an audit event to the audit subsystem
+ * @type: audit type for the message
+ * @profile: profile to check against
+ * @sa: audit event
+ * @cb: optional callback fn for type specific fields
+ *
+ * Handle default message switching based off of audit mode flags
+ */
+int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
+ void (*cb) (struct audit_buffer *, void *))
+{
+ struct audit_context *audit_cxt;
+ audit_cxt = aa_g_logsyscall ? current->audit_context : NULL;
+
+ if (type == AUDIT_APPARMOR_AUTO) {
+ if (likely(!sa->error)) {
+ if (PROFILE_AUDIT_MODE(profile) != AUDIT_ALL)
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else if (PROFILE_COMPLAIN(profile))
+ type = AUDIT_APPARMOR_ALLOWED;
+ else
+ type = AUDIT_APPARMOR_DENIED;
+ }
+ if (PROFILE_AUDIT_MODE(profile) == AUDIT_QUIET ||
+ (type == AUDIT_APPARMOR_DENIED &&
+ PROFILE_AUDIT_MODE(profile) == AUDIT_QUIET))
+ return sa->error;
+
+ return aa_audit_base(type, profile, sa, audit_cxt, cb);
+}
+
+/**
+ * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem
+ * @profile: profile to check against
+ * @gfp: memory allocation flags
+ * @msg: string describing syscall being rejected
+ * @cb: optional callback fn for type specific fields
+ */
+int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp,
+ const char *msg,
+ void (*cb) (struct audit_buffer *, void *))
+{
+ struct aa_audit sa = {
+ .operation = "syscall",
+ .info = msg,
+ .gfp_mask = gfp,
+ .error = -EACCES,
+ };
+
+ return aa_audit_base(AUDIT_APPARMOR_DENIED, profile, &sa,
+ current->audit_context, NULL);
+}
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
new file mode 100644
index 0000000..618527f
--- /dev/null
+++ b/security/apparmor/include/audit.h
@@ -0,0 +1,57 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor auditing function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_AUDIT_H
+#define __AA_AUDIT_H
+
+#include <linux/audit.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+struct aa_profile;
+
+extern const char *audit_mode_names[];
+#define AUDIT_MAX_INDEX 5
+
+#define AUDIT_APPARMOR_AUTO 0 /* auto choose audit message type */
+
+enum audit_mode {
+ AUDIT_NORMAL, /* follow normal auditing of accesses */
+ AUDIT_QUIET_DENIED, /* quiet all denied access messages */
+ AUDIT_QUIET, /* quiet all messages */
+ AUDIT_NOQUIET, /* do not quiet audit messages */
+ AUDIT_ALL /* audit all accesses */
+};
+
+/*
+ * 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 {
+ struct task_struct *task;
+ gfp_t gfp_mask;
+ int error;
+ const char *operation;
+ const char *info;
+};
+
+int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
+ void (*cb) (struct audit_buffer *, void *));
+
+int aa_audit_syscallreject(struct aa_profile *profile, gfp_t gfp, const char *,
+ void (*cb) (struct audit_buffer *, void *));
+
+#endif /* __AA_AUDIT_H */
--
1.6.3.3

2009-11-10 16:13:22

by John Johansen

[permalink] [raw]
Subject: [PATCH 03/12] AppArmor: contexts used in attaching policy to system objects

AppArmor contexts attach profiles and state to tasks, files, etc. when
a direct profile reference is not sufficient.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/context.c | 225 +++++++++++++++++++++++++++++++++++
security/apparmor/include/context.h | 145 ++++++++++++++++++++++
2 files changed, 370 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/context.c
create mode 100644 security/apparmor/include/context.h

diff --git a/security/apparmor/context.c b/security/apparmor/context.c
new file mode 100644
index 0000000..823207d
--- /dev/null
+++ b/security/apparmor/context.c
@@ -0,0 +1,225 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions used to manipulate object security
+ * contexts.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include "include/context.h"
+#include "include/policy.h"
+
+struct aa_task_context *aa_alloc_task_context(gfp_t flags)
+{
+ return kzalloc(sizeof(struct aa_task_context), flags);
+}
+
+void aa_free_task_context(struct aa_task_context *cxt)
+{
+ if (cxt) {
+ aa_put_profile(cxt->sys.profile);
+ aa_put_profile(cxt->sys.previous);
+ aa_put_profile(cxt->sys.onexec);
+
+ kzfree(cxt);
+ }
+}
+
+/**
+ * aa_dup_task_context - duplicate a task context, incrementing reference counts
+ * @new: a blank task context
+ * @old: the task context to copy
+ */
+void aa_dup_task_context(struct aa_task_context *new,
+ const struct aa_task_context *old)
+{
+ *new = *old;
+ aa_get_profile(new->sys.profile);
+ aa_get_profile(new->sys.previous);
+ aa_get_profile(new->sys.onexec);
+}
+
+/**
+ * aa_cred_policy - obtain cred's profiles
+ * @cred: cred to obtain profiles from
+ * @sys: return system profile
+ *
+ * does NOT increment reference count
+ */
+void aa_cred_policy(const struct cred *cred, struct aa_profile **sys)
+{
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+ *sys = aa_confining_profile(cxt->sys.profile);
+}
+
+/**
+ * aa_get_task_policy - get the cred with the task policy, and current profiles
+ * @task: task to get policy of
+ * @sys: return - pointer to system profile
+ *
+ * Returns: a refcounted task cred
+ *
+ * Only gets the cred ref count which has ref counts on the profiles returned
+ */
+struct cred *aa_get_task_policy(const struct task_struct *task,
+ struct aa_profile **sys)
+{
+ struct cred *cred = get_task_cred(task);
+ aa_cred_policy(cred, sys);
+ return cred;
+}
+
+/**
+ * replace_group - replace a context group profile
+ * @cgrp: profile
+ * @profile: profile to replace cxt group
+ *
+ * Replace context grouping profile reference with @profile
+ */
+static void replace_group(struct aa_task_cxt_group *cgrp,
+ struct aa_profile *profile)
+{
+ if (cgrp->profile == profile)
+ return;
+
+ if (!profile || (profile->flags & PFLAG_UNCONFINED) ||
+ (cgrp->profile && cgrp->profile->ns != profile->ns)) {
+ aa_put_profile(cgrp->previous);
+ aa_put_profile(cgrp->onexec);
+ cgrp->previous = NULL;
+ cgrp->onexec = NULL;
+ cgrp->token = 0;
+ }
+ aa_put_profile(cgrp->profile);
+ cgrp->profile = aa_get_profile(profile);
+}
+
+/**
+ * aa_replace_current_profiles - replace the current tasks profiles
+ * @sys: new system profile
+ *
+ * Returns: error on failure
+ */
+int aa_replace_current_profiles(struct aa_profile *sys)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ replace_group(&cxt->sys, sys);
+ /* todo add user group */
+
+ commit_creds(new);
+ return 0;
+}
+
+/**
+ * aa_set_current_onexec - set the tasks change_profile to happen onexec
+ * @sys: system profile to set at exec
+ *
+ * Returns: error on failure
+ */
+int aa_set_current_onexec(struct aa_profile *sys)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = aa_get_profile(sys);
+
+ commit_creds(new);
+ return 0;
+}
+
+/**
+ * aa_set_current_hat - set the current tasks hat
+ * @profile: profile to set as the current hat
+ * @token: token value that must be specified to change from the hat
+ *
+ * Do switch of tasks hat. If the task is currently in a hat
+ * validate the token to match.
+ *
+ * Returns: error on failure
+ */
+int aa_set_current_hat(struct aa_profile *profile, u64 token)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ if (!cxt->sys.previous) {
+ cxt->sys.previous = cxt->sys.profile;
+ cxt->sys.token = token;
+ } else if (cxt->sys.token == token) {
+ aa_put_profile(cxt->sys.profile);
+ } else {
+ /* previous_profile && cxt->token != token */
+ abort_creds(new);
+ return -EACCES;
+ }
+ cxt->sys.profile = aa_get_profile(profile);
+ /* clear exec on switching context */
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = NULL;
+
+ commit_creds(new);
+ return 0;
+}
+
+/**
+ * aa_restore_previous_profile - exit from hat context restoring the profile
+ * @token: the token that must be matched to exit hat context
+ *
+ * Attempt to return out of a hat to the previous profile. The token
+ * must match the stored token value.
+ *
+ * Returns: error of failure
+ */
+int aa_restore_previous_profile(u64 token)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ if (cxt->sys.token != token) {
+ abort_creds(new);
+ return -EACCES;
+ }
+ /* ignore restores when there is no saved profile */
+ if (!cxt->sys.previous) {
+ abort_creds(new);
+ return 0;
+ }
+
+ aa_put_profile(cxt->sys.profile);
+ cxt->sys.profile = aa_profile_newest(cxt->sys.previous);
+ if (unlikely(cxt->sys.profile != cxt->sys.previous)) {
+ aa_get_profile(cxt->sys.profile);
+ aa_put_profile(cxt->sys.previous);
+ }
+ /* clear exec && prev information when restoring to previous context */
+ cxt->sys.previous = NULL;
+ cxt->sys.token = 0;
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = NULL;
+
+ commit_creds(new);
+ return 0;
+}
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h
new file mode 100644
index 0000000..ffc83c6
--- /dev/null
+++ b/security/apparmor/include/context.h
@@ -0,0 +1,145 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor contexts used to associate "labels" to objects.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_CONTEXT_H
+#define __AA_CONTEXT_H
+
+#include <linux/cred.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+
+#include "policy.h"
+
+/* struct aa_file_cxt - the AppArmor context the file was opened in
+ * @profile: the profile the file was opened under
+ * @perms: the permission the file was opened with
+ */
+struct aa_file_cxt {
+ struct aa_profile *profile;
+ u16 allowed;
+};
+
+static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
+{
+ return kzalloc(sizeof(struct aa_file_cxt), gfp);
+}
+
+static inline void aa_free_file_context(struct aa_file_cxt *cxt)
+{
+ aa_put_profile(cxt->profile);
+ kzfree(cxt);
+}
+
+/* struct aa_task_cxt_group - a grouping label data for confined tasks
+ * @profile: the current profile
+ * @exec: profile to transition to on next exec
+ * @previous: profile the task may return to
+ * @token: magic value the task must know for returning to @previous_profile
+ *
+ * Contains the task's current profile (which could change due to
+ * change_hat). Plus the hat_magic needed during change_hat.
+ */
+struct aa_task_cxt_group {
+ struct aa_profile *profile;
+ struct aa_profile *onexec;
+ struct aa_profile *previous;
+ u64 token;
+};
+
+/**
+ * struct aa_task_context - primary label for confined tasks
+ * @sys: the system labeling for the task
+ *
+ * A task is confined by the intersection of its system and user profiles
+ */
+struct aa_task_context {
+ struct aa_task_cxt_group sys;
+};
+
+struct aa_task_context *aa_alloc_task_context(gfp_t flags);
+void aa_free_task_context(struct aa_task_context *cxt);
+void aa_dup_task_context(struct aa_task_context *new,
+ const struct aa_task_context *old);
+void aa_cred_policy(const struct cred *cred, struct aa_profile **sys);
+struct cred *aa_get_task_policy(const struct task_struct *task,
+ struct aa_profile **sys);
+int aa_replace_current_profiles(struct aa_profile *sys);
+void aa_put_task_policy(struct cred *cred);
+int aa_set_current_onexec(struct aa_profile *sys);
+int aa_set_current_hat(struct aa_profile *profile, u64 token);
+int aa_restore_previous_profile(u64 cookie);
+
+static inline struct aa_task_context *__aa_task_cxt(struct task_struct *task)
+{
+ return __task_cred(task)->security;
+}
+
+/**
+ * __aa_task_is_confined - determine if @task has any confinement
+ * @task: task to check confinement of
+ *
+ * If @task != current needs to be in RCU safe critical section
+ */
+static inline int __aa_task_is_confined(struct task_struct *task)
+{
+ struct aa_task_context *cxt;
+ int rc = 1;
+
+ cxt = __aa_task_cxt(task);
+ if (!cxt || (cxt->sys.profile->flags & PFLAG_UNCONFINED))
+ rc = 0;
+
+ return rc;
+}
+
+static inline const struct cred *aa_current_policy(struct aa_profile **sys)
+{
+ const struct cred *cred = current_cred();
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+ *sys = aa_confining_profile(cxt->sys.profile);
+
+ return cred;
+}
+
+static inline const struct cred *aa_current_policy_wupd(struct aa_profile **sys)
+{
+ const struct cred *cred = current_cred();
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+
+ *sys = aa_profile_newest(cxt->sys.profile);
+ if (unlikely((cxt->sys.profile != *sys)))
+ aa_replace_current_profiles(*sys);
+ *sys = aa_filter_profile(*sys);
+
+ return cred;
+}
+
+static inline struct aa_profile *aa_current_profile(void)
+{
+ const struct cred *cred = current_cred();
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+ return aa_confining_profile(cxt->sys.profile);
+}
+
+static inline struct aa_profile *aa_current_profile_wupd(void)
+{
+ struct aa_profile *p;
+ aa_current_policy_wupd(&p);
+ return p;
+}
+
+#endif /* __AA_CONTEXT_H */
--
1.6.3.3

2009-11-10 16:13:31

by John Johansen

[permalink] [raw]
Subject: [PATCH 04/12] AppArmor: core policy routines

The basic routines and defines for AppArmor policy. AppArmor policy
is defined by a few basic components.
profiles - the basic unit of confinement contain all the information
to enforce policy on a task

Profiles tend to be named after an executable that they
will attach to but this is not required.
namespaces - a container for a set of profiles that will be used
during attachment and transitions between profiles.
sids - which provide a unique id for each profile

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/policy.h | 321 +++++++++++
security/apparmor/include/sid.h | 45 ++
security/apparmor/policy.c | 1034 ++++++++++++++++++++++++++++++++++++
security/apparmor/sid.c | 108 ++++
4 files changed, 1508 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/policy.h
create mode 100644 security/apparmor/include/sid.h
create mode 100644 security/apparmor/policy.c
create mode 100644 security/apparmor/sid.c

diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
new file mode 100644
index 0000000..a53d9a4
--- /dev/null
+++ b/security/apparmor/include/policy.h
@@ -0,0 +1,321 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_POLICY_H
+#define __AA_POLICY_H
+
+#include <linux/capability.h>
+#include <linux/cred.h>
+#include <linux/kref.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+
+#include "apparmor.h"
+#include "audit.h"
+#include "capability.h"
+#include "domain.h"
+#include "file.h"
+#include "net.h"
+#include "resource.h"
+
+extern const char *profile_mode_names[];
+#define APPARMOR_NAMES_MAX_INDEX 3
+
+#define PROFILE_COMPLAIN(_profile) \
+ ((aa_g_profile_mode == APPARMOR_COMPLAIN) || ((_profile) && \
+ (_profile)->mode == APPARMOR_COMPLAIN))
+
+#define PROFILE_KILL(_profile) \
+ ((aa_g_profile_mode == APPARMOR_KILL) || ((_profile) && \
+ (_profile)->mode == APPARMOR_KILL))
+
+#define PROFILE_IS_HAT(_profile) \
+ ((_profile) && (_profile)->flags & PFLAG_HAT)
+
+/*
+ * FIXME: currently need a clean way to replace and remove profiles as a
+ * set. It should be done at the namespace level.
+ * Either, with a set of profiles loaded at the namespace level or via
+ * a mark and remove marked interface.
+ */
+enum profile_mode {
+ APPARMOR_ENFORCE, /* enforce access rules */
+ APPARMOR_COMPLAIN, /* allow and log access violations */
+ APPARMOR_KILL, /* kill task on access violation */
+};
+
+enum profile_flags {
+ PFLAG_HAT = 1, /* profile is a hat */
+ PFLAG_UNCONFINED = 2, /* profile is the unconfined profile */
+ PFLAG_NULL = 4, /* profile is null learning profile */
+ PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
+ PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
+ PFLAG_USER_DEFINED = 0x20, /* user based profile */
+ PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
+ PFLAG_MMAP_MIN_ADDR = 0x80, /* profile controls mmap_min_addr */
+ PFLAG_DELETED_NAMES = 0x100, /* mediate deleted paths */
+ PFLAG_CONNECT_PATH = 0x200, /* connect disconnected paths to / */
+};
+
+#define AA_NEW_SID 0
+
+struct aa_profile;
+
+/* struct aa_policy_common - common part of both namespaces and profiles
+ * @name: name of the object
+ * @count: reference count of the obj
+ * lock: lock for modifying the object
+ * @list: list object is on
+ * @profiles: head of the profiles list contained in the object
+ */
+struct aa_policy_common {
+ char *name;
+ struct kref count;
+ rwlock_t lock;
+ struct list_head list;
+ struct list_head profiles;
+};
+
+/* struct aa_ns_acct - accounting of profiles in namespace
+ * @max_size: maximum space allowed for all profiles in namespace
+ * @max_count: maximum number of profiles that can be in this namespace
+ * @size: current size of profiles
+ * @count: current count of profiles (includes null profiles)
+ */
+struct aa_ns_acct {
+ int max_size;
+ int max_count;
+ int size;
+ int count;
+};
+
+/* struct aa_namespace - namespace for a set of profiles
+ * @name: the name of the namespace
+ * @list: list the namespace is on
+ * @profiles: list of profile in the namespace
+ * @acct: accounting for the namespace
+ * @profile_count: count of profiles on @profiles list
+ * @size: accounting of how much memory is consumed by the contained profiles
+ * @unconfined: special unconfined profile for the namespace
+ * @count: reference count on the namespace
+ * @lock: lock for adding/removing profile to the namespace
+ *
+ * An aa_namespace defines the set profiles that are searched to determine
+ * which profile to attach to a task. Profiles can not be shared between
+ * aa_namespaces and profile names within a namespace are guarenteed to be
+ * unique. When profiles in seperate namespaces have the same name they
+ * are NOT considered to be equivalent.
+ *
+ * Namespace names must be unique and can not contain the characters :/\0
+ *
+ * FIXME TODO: add vserver support so a vserer gets a default namespace
+ */
+struct aa_namespace {
+ struct aa_policy_common base;
+ struct aa_ns_acct acct;
+ int is_stale;
+ struct aa_profile *unconfined;
+};
+
+/* struct aa_profile - basic confinement data
+ * @base - base componets of the profile (name, refcount, lists, lock ...)
+ * @fqname - The fully qualified profile name, less the namespace name
+ * @ns: namespace the profile is in
+ * @parent: parent profile of this profile, if one exists
+ * @replacedby: is set profile that replaced this profile
+ * @xmatch: optional extended matching for unconfined executables names
+ * @xmatch_plen: xmatch prefix len, used to determine xmatch priority
+ * @sid: the unique security id number of this profile
+ * @audit: the auditing mode of the profile
+ * @mode: the enforcement mode of the profile
+ * @flags: flags controlling profile behavior
+ * @size: the memory consumed by this profiles rules
+ * @file: The set of rules governing basic file access and domain transitions
+ * @caps: capabilities for the profile
+ * @net: network controls for the profile
+ * @rlimits: rlimits for the profile
+ *
+ * The AppArmor profile contains the basic confinement data. Each profile
+ * has a name, and exist in a namespace. The @name and @exec_match are
+ * used to determine profile attachment against unconfined tasks. All other
+ * attachments are determined by in profile X transition rules.
+ *
+ * The @replacedby field is write protected by the profile lock. Reads
+ * are assumed to be atomic, and are done without locking.
+ *
+ * Profiles have a hierachy where hats and children profiles keep
+ * a reference to their parent.
+ *
+ * Profile names can not begin with a : and can not contain the \0
+ * character. If a profile name begins with / it will be considered when
+ * determining profile attachment on "unconfined" tasks.
+ */
+struct aa_profile {
+ struct aa_policy_common base;
+ char *fqname;
+
+ struct aa_namespace *ns;
+ struct aa_profile *parent;
+ struct aa_profile *replacedby;
+
+ struct aa_dfa *xmatch;
+ int xmatch_len;
+ u32 sid;
+ enum audit_mode audit;
+ enum profile_mode mode;
+ u32 flags;
+ int size;
+
+ unsigned long mmap_min_addr;
+
+ struct aa_file_rules file;
+ struct aa_caps caps;
+ struct aa_net net;
+ struct aa_rlimit rlimits;
+};
+
+extern struct list_head ns_list;
+extern rwlock_t ns_list_lock;
+
+extern struct aa_namespace *default_namespace;
+extern enum profile_mode aa_g_profile_mode;
+
+void aa_add_profile(struct aa_policy_common *common,
+ struct aa_profile *profile);
+
+int aa_alloc_default_namespace(void);
+void aa_free_default_namespace(void);
+struct aa_namespace *alloc_aa_namespace(const char *name);
+void free_aa_namespace_kref(struct kref *kref);
+void free_aa_namespace(struct aa_namespace *ns);
+
+struct aa_namespace *aa_find_namespace(const char *name);
+struct aa_namespace *aa_prepare_namespace(const char *name);
+void aa_profile_ns_list_release(void);
+
+static inline struct aa_policy_common *aa_get_common(struct aa_policy_common *c)
+{
+ if (c)
+ kref_get(&c->count);
+
+ return c;
+}
+
+static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ kref_get(&(ns->base.count));
+
+ return ns;
+}
+
+static inline void aa_put_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ kref_put(&ns->base.count, free_aa_namespace_kref);
+}
+
+struct aa_profile *alloc_aa_profile(const char *name);
+struct aa_profile *aa_alloc_null_profile(struct aa_profile *parent, int hat);
+void free_aa_profile_kref(struct kref *kref);
+void free_aa_profile(struct aa_profile *profile);
+struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
+struct aa_profile *aa_find_profile(struct aa_namespace *ns, const char *name);
+struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
+
+ssize_t aa_interface_add_profiles(void *data, size_t size);
+ssize_t aa_interface_replace_profiles(void *udata, size_t size);
+ssize_t aa_interface_remove_profiles(char *name, size_t size);
+
+/**
+ * aa_filter_profile - filter out profiles that shouldn't be used to mediate
+ * @profile: profile to filter
+ *
+ * does not change refcounts
+ *
+ * Return: @profile or NULL if it is filtered
+ */
+static inline struct aa_profile *aa_filter_profile(struct aa_profile *profile)
+{
+ if (profile->flags & PFLAG_UNCONFINED)
+ return NULL;
+ return profile;
+}
+
+
+/**
+ * aa_profile_newest - find the newest version of @profile
+ * @profile: the profile to check for newer versions of
+ *
+ * Find the newest version of @profile, if @profile is the newest version
+ * return @profile.
+ *
+ * NOTE: the profile returned is not refcounted, The refcount on @profile
+ * must be held until the caller decides what to do with the returned newest
+ * version.
+ */
+static inline struct aa_profile *aa_profile_newest(struct aa_profile *profile)
+{
+ if (unlikely(profile && profile->replacedby))
+ for (; profile->replacedby; profile = profile->replacedby) ;
+
+ return profile;
+}
+
+/**
+ * aa_confining_profile - find the newest confining profile version
+ * @p - profile to check if newest version
+ *
+ * NOTE: the profile returned is not refcounted, The refcount on @p
+ * must be held until the caller decides what to do with the returned newest
+ * version.
+ */
+static inline struct aa_profile *aa_confining_profile(struct aa_profile *p)
+{
+ return aa_filter_profile(aa_profile_newest(p));
+}
+
+/**
+ * aa_get_profile - increment refcount on profile @p
+ * @p: profile
+ */
+static inline struct aa_profile *aa_get_profile(struct aa_profile *p)
+{
+ if (p)
+ kref_get(&(p->base.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->base.count, free_aa_profile_kref);
+}
+
+static inline int PROFILE_AUDIT_MODE(struct aa_profile *profile)
+{
+ if (aa_g_audit != AUDIT_NORMAL)
+ return aa_g_audit;
+ if (profile)
+ return profile->audit;
+ return AUDIT_NORMAL;
+}
+
+#endif /* __AA_POLICY_H */
diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h
new file mode 100644
index 0000000..0f5df2f
--- /dev/null
+++ b/security/apparmor/include/sid.h
@@ -0,0 +1,45 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor security identifier (sid) definitions
+ *
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_SID_H
+#define __AA_SID_H
+
+#include <linux/types.h>
+
+struct aa_profile;
+
+#define AA_ALLOC_USR_SID 1
+#define AA_ALLOC_SYS_SID 0
+
+u32 aa_alloc_sid(int is_usr);
+void aa_free_sid(u32 sid);
+int aa_add_sid_profile(u32 sid, struct aa_profile *profile);
+int aa_replace_sid_profile(u32 sid, struct aa_profile *profile);
+struct aa_profile *aa_get_sid_profile(u32 sid);
+
+static inline u32 aa_compound_sid(u32 sys, u32 usr)
+{
+ return sys | usr;
+}
+
+static inline u32 aa_usr_sid(u32 sid)
+{
+ return sid & 0xffff0000;
+}
+
+static inline u32 aa_sys_sid(u32 sid)
+{
+ return sid & 0xffff;
+}
+
+#endif /* __AA_SID_H */
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
new file mode 100644
index 0000000..22b2be9
--- /dev/null
+++ b/security/apparmor/policy.c
@@ -0,0 +1,1034 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy manipulation functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 policy is based around profiles, which contain the rules a
+ * task is confined by. Every task in the sytem has a profile attached
+ * to it determined either by matching "unconfined" tasks against the
+ * visible set of profiles or by following a profiles attachment rules.
+ *
+ * Each profile exists in an profile namespace which is a container of
+ * related profiles. Each namespace contains a special "unconfined" profile,
+ * which doesn't enfforce any confinement on a task beyond DAC.
+ *
+ * Namespace and profile names can be written together in either
+ * of two syntaxes.
+ * :namespace:profile - used by kernel interfaces for easy detection
+ * namespace://profile - used by policy
+ *
+ * Profile names can not start with : or @ or ^ and may not contain \0
+ *
+ * Reserved profile names
+ * unconfined - special automatically generated unconfined profile
+ * inherit - special name to indicate profile inheritance
+ * null-XXXX-YYYY - special automically generated learning profiles
+ *
+ * Namespace names may not start with / or @ and may not contain \0 or :
+ * Reserved namespace namespace
+ * default - the default namespace setup by AppArmor
+ * user-XXXX - user defined profiles
+ *
+ * a // in a profile or namespace name indicates a compound name with the
+ * name before the // being the parent and the name after the child.
+ *
+ * Profile and namespace hierachies serve two different but similar purposes.
+ * The namespace contains the set of visible profiles that are considered
+ * for attachment. The hierarchy of namespaces allows for virtualizing
+ * the namespace so that for example a chroot can have its own set of profiles
+ * which may define some local user namespaces.
+ * The profile hierachy severs two distinct purposes,
+ * - it allows for sub profiles or hats, which allows an application to run
+ * subprograms under its own profile with different restriction than it
+ * self, and not have it use the system profile.
+ * eg. if a mail program starts an editor, the policy might make the
+ * restrictions tighter on the editor tighter than the mail program,
+ * and definitely different than general editor restrictions
+ * - it allows for binary hierarchy of profiles, so that execution history
+ * is preserved. This feature isn't exploited by AppArmor reference policy
+ * but is allowed. NOTE: this is currently suboptimal because profile
+ * aliasing is not currently implemented so that a profile for each
+ * level must be defined.
+ * eg. /bin/bash///bin/ls as a name would indicate /bin/ls was started
+ * from /bin/bash
+ *
+ * NOTES:
+ * - hierarchical namespaces are not currently implemented. Currently
+ * there is only a flat set of namespaces.
+ * - locking of profile lists is currently fairly coarse. All profile
+ * lists within a namespace use the namespace lock.
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "include/apparmor.h"
+#include "include/capability.h"
+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/match.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+#include "include/resource.h"
+#include "include/sid.h"
+
+/* list of profile namespaces and lock */
+LIST_HEAD(ns_list);
+DEFINE_RWLOCK(ns_list_lock);
+
+struct aa_namespace *default_namespace;
+
+const char *profile_mode_names[] = {
+ "enforce",
+ "complain",
+ "kill",
+};
+
+static int common_init(struct aa_policy_common *common, const char *name)
+{
+ /* freed by common_free */
+ common->name = kstrdup(name, GFP_KERNEL);
+ if (!common->name)
+ return 0;
+ INIT_LIST_HEAD(&common->list);
+ INIT_LIST_HEAD(&common->profiles);
+ kref_init(&common->count);
+ rwlock_init(&common->lock);
+
+ return 1;
+}
+
+static void common_free(struct aa_policy_common *common)
+{
+ /* still contains profiles -- invalid */
+ if (!list_empty(&common->profiles)) {
+ AA_ERROR("%s: internal error, "
+ "policy '%s' still contains profiles\n",
+ __func__, common->name);
+ BUG();
+ }
+ if (!list_empty(&common->list)) {
+ AA_ERROR("%s: internal error, policy '%s' still on list\n",
+ __func__, common->name);
+ BUG();
+ }
+
+ kfree(common->name);
+}
+
+static struct aa_policy_common *__common_find(struct list_head *head,
+ const char *name)
+{
+ struct aa_policy_common *common;
+
+ list_for_each_entry(common, head, list) {
+ if (!strcmp(common->name, name))
+ return common;
+ }
+ return NULL;
+}
+
+static struct aa_policy_common *__common_strn_find(struct list_head *head,
+ const char *str, int len)
+{
+ struct aa_policy_common *common;
+
+ list_for_each_entry(common, head, list) {
+ if (aa_strneq(common->name, str, len))
+ return common;
+ }
+
+ return NULL;
+}
+
+/*
+ * Routines for AppArmor namespaces
+ */
+
+/**
+ * aa_alloc_default_namespace - allocate the base default namespace
+ *
+ * Returns 0 on success else error
+ *
+ */
+int aa_alloc_default_namespace(void)
+{
+ struct aa_namespace *ns;
+ /* released by aa_free_default_namespace - used as list ref*/
+ ns = alloc_aa_namespace("default");
+ if (!ns)
+ return -ENOMEM;
+
+ /* released by aa_free_default_namespace - global var ref*/
+ default_namespace = aa_get_namespace(ns);
+ write_lock(&ns_list_lock);
+ list_add(&ns->base.list, &ns_list);
+ write_unlock(&ns_list_lock);
+
+ return 0;
+}
+
+void aa_free_default_namespace(void)
+{
+ write_lock(&ns_list_lock);
+ list_del_init(&default_namespace->base.list);
+ write_unlock(&ns_list_lock);
+ /* drop the list ref and the global default_namespace ref */
+ aa_put_namespace(default_namespace);
+ aa_put_namespace(default_namespace);
+ default_namespace = NULL;
+}
+
+/**
+ * alloc_aa_namespace - allocate, initialize and return a new namespace
+ * @name: a preallocated name
+ * Returns NULL on failure.
+ */
+struct aa_namespace *alloc_aa_namespace(const char *name)
+{
+ struct aa_namespace *ns;
+
+ ns = kzalloc(sizeof(*ns), GFP_KERNEL);
+ AA_DEBUG("%s(%p)\n", __func__, ns);
+ if (!ns)
+ return NULL;
+
+ if (!common_init(&ns->base, name))
+ goto fail_ns;
+
+ /*
+ * null profile is not added to the profile list,
+ * released by free_aa_namespace
+ */
+ ns->unconfined = alloc_aa_profile("unconfined");
+ if (!ns->unconfined)
+ goto fail_unconfined;
+
+ ns->unconfined->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR |
+ PFLAG_IMMUTABLE;
+
+ /*
+ * released by free_aa_namespace, however aa_remove_namespace breaks
+ * the cyclic references (ns->unconfined, and unconfinged->ns) and
+ * replaces with refs to default namespace unconfined
+ */
+ ns->unconfined->ns = aa_get_namespace(ns);
+
+ return ns;
+
+fail_unconfined:
+ kfree(ns->base.name);
+fail_ns:
+ kfree(ns);
+ return NULL;
+}
+
+/**
+ * free_aa_namespace_kref - free aa_namespace by kref (see aa_put_namespace)
+ * @kr: kref callback for freeing of a namespace
+ */
+void free_aa_namespace_kref(struct kref *kref)
+{
+ free_aa_namespace(container_of(kref, struct aa_namespace, base.count));
+}
+
+/**
+ * free_aa_namespace - free a profile namespace
+ * @namespace: the namespace to free
+ *
+ * Requires: All references to the namespace must have been put, if the
+ * namespace was referenced by a profile confining a task,
+ */
+void free_aa_namespace(struct aa_namespace *ns)
+{
+ if (!ns)
+ return;
+
+ common_free(&ns->base);
+
+ if (ns->unconfined && ns->unconfined->ns == ns)
+ ns->unconfined->ns = NULL;
+
+ aa_put_profile(ns->unconfined);
+ kzfree(ns);
+}
+
+/**
+ * __aa_find_namespace - find a namespace on a list by @name
+ * @name - name of namespace to look for
+ *
+ * Return: unrefcounted namespace
+ *
+ * Requires: ns_list_lock be held
+ */
+static struct aa_namespace *__aa_find_namespace(struct list_head *head,
+ const char *name)
+{
+ return (struct aa_namespace *)__common_find(head, name);
+}
+
+/**
+ * aa_find_namespace - look up a profile namespace on the namespace list
+ * @name: name of namespace to find
+ *
+ * Return: a pointer to the namespace on the list, or NULL if no namespace
+ * called @name exists.
+ *
+ * refcount released by caller
+ */
+struct aa_namespace *aa_find_namespace(const char *name)
+{
+ struct aa_namespace *ns = NULL;
+
+ read_lock(&ns_list_lock);
+ ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
+ read_unlock(&ns_list_lock);
+
+ return ns;
+}
+
+/**
+ * aa_prepare_namespace - find an existing or create a new namespace of @name
+ * @name: the namespace to find or add
+ *
+ * Return: refcounted namespace or NULL if failed to create one
+ */
+struct aa_namespace *aa_prepare_namespace(const char *name)
+{
+ struct aa_namespace *ns;
+
+ write_lock(&ns_list_lock);
+ if (name)
+ /* released by caller */
+ ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
+ else
+ /* released by caller */
+ ns = aa_get_namespace(default_namespace);
+ if (!ns) {
+ struct aa_namespace *new_ns;
+ write_unlock(&ns_list_lock);
+ new_ns = alloc_aa_namespace(name);
+ if (!new_ns)
+ return NULL;
+ write_lock(&ns_list_lock);
+ /* test for race when new_ns was allocated */
+ ns = __aa_find_namespace(&ns_list, name);
+ if (!ns) {
+ list_add(&new_ns->base.list, &ns_list);
+ /* add list ref */
+ ns = aa_get_namespace(new_ns);
+ } else {
+ /* raced so free the new one */
+ free_aa_namespace(new_ns);
+ /* get reference on namespace */
+ aa_get_namespace(ns);
+ }
+ }
+ write_unlock(&ns_list_lock);
+
+ /* return ref */
+ return ns;
+}
+
+/**
+ * __aa_add_profile - add a profile to a list
+ * @common: the namespace or profile list to add it to
+ * @profile: the profile to add
+ *
+ * refcount @profile, should be put by __aa_remove_profile
+ *
+ * Requires: namespace list lock be held, or list not be shared
+ */
+void __aa_add_profile(struct aa_policy_common *common,
+ struct aa_profile *profile)
+{
+ list_add(&profile->base.list, &common->profiles);
+ if (!(profile->flags & PFLAG_NO_LIST_REF))
+ /* get list reference */
+ aa_get_profile(profile);
+}
+
+/**
+ * __aa_remove_profile - remove a profile from the list it is one
+ * @profile: the profile to remove
+ *
+ * remove a profile from the list, warning generally removal should
+ * be done with __aa_replace_profile as most profile removals are
+ * should be replacements to the unconfined profile.
+ *
+ * put @profile refcount
+ *
+ * Requires: namespace list lock be held, or list not be shared
+ */
+void __aa_remove_profile(struct aa_profile *profile)
+{
+ list_del_init(&profile->base.list);
+ if (!(profile->flags & PFLAG_NO_LIST_REF))
+ /* release list reference */
+ aa_put_profile(profile);
+}
+
+/**
+ * __aa_replace_profile - replace @old with @new on a list
+ * @old: profile to be replaced
+ * @new: profile to replace @old with
+ *
+ * Will duplicaticate and refcount elements that @new inherits from @old
+ * and will inherit @old children. If new is NULL it will replace to the
+ *
+ *
+ * refcount @new for list, put @old list refcount
+ *
+ * Requires: namespace list lock be held, or list not be shared
+ */
+void __aa_replace_profile(struct aa_profile *old,
+ struct aa_profile *new)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *child, *tmp;
+
+ if (old->parent)
+ common = &old->parent->base;
+ else
+ common = &old->ns->base;
+
+ if (new) {
+ /* released when @new is freed */
+ new->parent = aa_get_profile(old->parent);
+ new->ns = aa_get_namespace(old->ns);
+ new->sid = old->sid;
+ __aa_add_profile(common, new);
+ } else {
+ /* refcount not taken, held via @old refcount */
+ new = old->ns->unconfined;
+ }
+
+ /* inherit children */
+ list_for_each_entry_safe(child, tmp, &old->base.profiles, base.list) {
+ aa_put_profile(child->parent);
+ child->parent = aa_get_profile(new);
+ /* list refcount transfered to @new*/
+ list_move(&child->base.list, &new->base.profiles);
+ }
+
+ /* released by free_aa_profile */
+ old->replacedby = aa_get_profile(new);
+ __aa_remove_profile(old);
+}
+
+/**
+ * __aa_profile_list_release - remove all profiles on the list and put refs
+ * @head: list of profiles
+ *
+ * Requires: namespace lock be held
+ */
+void __aa_profile_list_release(struct list_head *head)
+{
+ struct aa_profile *profile, *tmp;
+ list_for_each_entry_safe(profile, tmp, head, base.list) {
+ /* release any children lists first */
+ __aa_profile_list_release(&profile->base.profiles);
+ __aa_replace_profile(profile, NULL);
+ }
+}
+
+/**
+ * __aa_remove_namespace - remove a namespace and all its children
+ * @ns: namespace to be removed
+ *
+ * Requires: ns_list_lock && ns->base.lock be held
+ */
+void __aa_remove_namespace(struct aa_namespace *ns)
+{
+ struct aa_profile *unconfined = ns->unconfined;
+ /* remove ns from namespace list */
+ list_del_init(&ns->base.list);
+
+ /*
+ * break the ns, unconfined profile cyclic reference and forward
+ * all new unconfined profiles requests to the default namespace
+ * This will result in all confined tasks that have a profile
+ * being removed inheriting the default->unconfined profile.
+ */
+ ns->unconfined = aa_get_profile(default_namespace->unconfined);
+ __aa_profile_list_release(&ns->base.profiles);
+ /* release original ns->unconfined ref */
+ aa_put_profile(unconfined);
+ /* release ns->base.list ref, from removal above */
+ aa_put_namespace(ns);
+}
+
+
+/**
+ * aa_profilelist_release - remove all namespaces and all associated profiles
+ */
+void aa_profile_ns_list_release(void)
+{
+ struct aa_namespace *ns, *tmp;
+
+ /* Remove and release all the profiles on namespace profile lists. */
+ write_lock(&ns_list_lock);
+ list_for_each_entry_safe(ns, tmp, &ns_list, base.list) {
+ write_lock(&ns->base.lock);
+ __aa_remove_namespace(ns);
+ write_unlock(&ns->base.lock);
+ }
+ write_unlock(&ns_list_lock);
+}
+
+static const char *fqname_subname(const char *name)
+{
+ char *split;
+ /* check for namespace which begins with a : and ends with : or \0 */
+ name = strstrip((char *)name);
+ if (*name == ':') {
+ split = aa_strchrnul(name + 1, ':');
+ if (*split == '\0')
+ return NULL;
+ name = strstrip(split + 1);
+ }
+ for (split = strstr(name, "//"); split; split = strstr(name, "//"))
+ name = split + 2;
+
+ return name;
+}
+
+/**
+ * alloc_aa_profile - allocate, initialize and return a new profile
+ * @fqname: name of the profile
+ *
+ * Returns NULL on failure, else refcounted profile
+ */
+struct aa_profile *alloc_aa_profile(const char *fqname)
+{
+ struct aa_profile *profile;
+
+ /* freed by free_aa_profile - usually through aa_put_profile */
+ profile = kzalloc(sizeof(*profile), GFP_KERNEL);
+ if (!profile)
+ return NULL;
+
+ if (!common_init(&profile->base, fqname)) {
+ kfree(profile);
+ return NULL;
+ }
+
+ profile->fqname = profile->base.name;
+ /* base.name is a substring of fqname */
+ profile->base.name =
+ (char *)fqname_subname((const char *)profile->fqname);
+
+ /* return ref */
+ return profile;
+}
+
+/**
+ * aa_new_null_profile - create a new null-X learning profile
+ * @parent: profile that caused this profile to be created
+ * @hat: true if the null- learning profile is a hat
+ *
+ * Create a null- complain mode profile used in learning mode. The name of
+ * the profile is unique and follows the format of parent//null-sid.
+ *
+ * null profiles are added to the profile list but the list does not
+ * hold a count on them so that they are automatically released when
+ * not in use.
+ */
+struct aa_profile *aa_alloc_null_profile(struct aa_profile *parent, int hat)
+{
+ struct aa_profile *profile = NULL;
+ char *name;
+ u32 sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+
+ /* freed below */
+ name = kmalloc(strlen(parent->fqname) + 2 + 7 + 8, GFP_KERNEL);
+ if (!name)
+ goto fail;
+ sprintf(name, "%s//null-%x", parent->fqname, sid);
+
+ profile = alloc_aa_profile(name);
+ kfree(name);
+ if (!profile)
+ goto fail;
+
+ profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ profile->mode = APPARMOR_COMPLAIN;
+ profile->flags = PFLAG_NULL | PFLAG_NO_LIST_REF;
+ if (hat)
+ profile->flags |= PFLAG_HAT;
+
+ /* released on free_aa_profile */
+ profile->parent = aa_get_profile(parent);
+ profile->ns = aa_get_namespace(parent->ns);
+
+ write_lock(&profile->ns->base.lock);
+ __aa_add_profile(&parent->base, profile);
+ write_unlock(&profile->ns->base.lock);
+
+ return profile;
+
+fail:
+ aa_free_sid(sid);
+ return NULL;
+}
+
+/**
+ * free_aa_profile_kref - free aa_profile by kref (called by aa_put_profile)
+ * @kr: kref callback for freeing of a profile
+ */
+void free_aa_profile_kref(struct kref *kref)
+{
+ struct aa_profile *p = container_of(kref, struct aa_profile,
+ base.count);
+
+ free_aa_profile(p);
+}
+
+/**
+ * free_aa_profile - free a profile
+ * @profile: the profile to free
+ *
+ * Free a profile, its hats and null_profile. All references to the profile,
+ * its hats and null_profile must have been put.
+ *
+ * If the profile was referenced from a task context, free_aa_profile() will
+ * be called from an rcu callback routine, so we must not sleep here.
+ */
+void free_aa_profile(struct aa_profile *profile)
+{
+ AA_DEBUG("%s(%p)\n", __func__, profile);
+
+ if (!profile)
+ return;
+
+ /*
+ * profile can still be on the list if the list doesn't hold a
+ * reference. There is no race as NULL profiles can't be attached
+ */
+ if (!list_empty(&profile->base.list)) {
+ if ((profile->flags & PFLAG_NULL) && profile->ns) {
+ write_lock(&profile->ns->base.lock);
+ list_del_init(&profile->base.list);
+ write_unlock(&profile->ns->base.lock);
+ } else {
+ AA_ERROR("%s: internal error, "
+ "profile '%s' still on ns list\n",
+ __func__, profile->base.name);
+ BUG();
+ }
+ }
+
+ /* profile->name is a substring of fqname */
+ profile->base.name = NULL;
+ /* free children profiles */
+ common_free(&profile->base);
+
+ BUG_ON(!list_empty(&profile->base.profiles));
+
+ kfree(profile->fqname);
+
+ aa_put_namespace(profile->ns);
+ aa_put_profile(profile->parent);
+
+ aa_free_file_rules(&profile->file);
+ aa_free_cap_rules(&profile->caps);
+ aa_free_net_rules(&profile->net);
+ aa_free_rlimit_rules(&profile->rlimits);
+
+ aa_free_sid(profile->sid);
+ aa_match_free(profile->xmatch);
+
+ if (profile->replacedby && !PTR_ERR(profile->replacedby))
+ aa_put_profile(profile->replacedby);
+
+ kzfree(profile);
+}
+
+/* TODO: profile count accounting - setup in remove */
+
+/**
+ * __aa_find_child - find a profile on @head list with a name matching @name
+ * @head: list to search
+ * @name: name of profile
+ *
+ * Requires: ns lock protecting list be held
+ *
+ * Returns unrefcounted profile ptr, or NULL if not found
+ */
+static struct aa_profile *__aa_find_child(struct list_head *head,
+ const char *name)
+{
+ return (struct aa_profile *)__common_find(head, name);
+}
+
+/**
+ * __aa_strn_find_child - find a profile on @head list using substring of @name
+ * @head: list to search
+ * @name: name of profile
+ * @len: length of @name substring to match
+ *
+ * Requires: ns lock protecting list be held
+ *
+ * Returns unrefcounted profile ptr, or NULL if not found
+ */
+static struct aa_profile *__aa_strn_find_child(struct list_head *head,
+ const char *name, int len)
+{
+ return (struct aa_profile *)__common_strn_find(head, name, len);
+}
+
+/**
+ * aa_find_child - find a profile by @name in @parent
+ * @parent: profile to search
+ * @name: profile name to search for
+ *
+ * Returns a ref counted profile or NULL if not found
+ */
+struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)
+{
+ struct aa_profile *profile;
+
+ read_lock(&parent->ns->base.lock);
+ profile = aa_get_profile(__aa_find_child(&parent->base.profiles, name));
+ read_unlock(&parent->ns->base.lock);
+
+ return profile;
+}
+
+/**
+ * __aa_find_parent - lookup the parent of a profile of name @fqname
+ * @ns: namespace to lookup profile in
+ * @fqname: fully qualified profile name to find parent of
+ *
+ * Lookups up the parent of a fully qualified profile name, the profile
+ * that matches fqname does not need to exist, in general this
+ * is used to load a new profile.
+ *
+ * Requires: ns->base.lock be held
+ *
+ * Returns: unrefcounted common or NULL if not found
+ */
+static struct aa_policy_common *__aa_find_parent(struct aa_namespace *ns,
+ const char *fqname)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *profile = NULL;
+ char *split;
+
+ common = &ns->base;
+
+ for (split = strstr(fqname, "//"); split;) {
+ profile = __aa_strn_find_child(&common->profiles, fqname,
+ split - fqname);
+ if (!profile)
+ return NULL;
+ common = &profile->base;
+ fqname = split + 2;
+ split = strstr(fqname, "//");
+ }
+ if (!profile)
+ return &ns->base;
+ return &profile->base;
+}
+
+/**
+ * __aa_find_profile - lookup the profile matching @fqname
+ * @ns: namespace to search for profile in
+ * @fqname: fully qualified profile name
+ *
+ * Requires: ns->base.lock be held
+ *
+ * Returns: unrefcounted profile pointer or NULL if not found
+ */
+static struct aa_profile *__aa_find_profile(struct aa_namespace *ns,
+ const char *fqname)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *profile = NULL;
+ char *split;
+
+ common = &ns->base;
+ for (split = strstr(fqname, "//"); split;) {
+ profile = __aa_strn_find_child(&common->profiles, fqname,
+ split - fqname);
+ if (!profile)
+ return NULL;
+
+ common = &profile->base;
+ fqname = split + 2;
+ split = strstr(fqname, "//");
+ }
+
+ profile = __aa_find_child(&common->profiles, fqname);
+
+ return profile;
+}
+
+/**
+ * aa_find_profile_by_name - find a profile by its full or partial name
+ * @ns: the namespace to start from
+ * @fqname: name to do lookup on. Does not contain namespace prefix
+ *
+ * Returns: refcounted profile or NULL if not found
+ */
+struct aa_profile *aa_find_profile(struct aa_namespace *ns, const char *fqname)
+{
+ struct aa_profile *profile;
+
+ read_lock(&ns->base.lock);
+ profile = aa_get_profile(__aa_find_profile(ns, fqname));
+ read_unlock(&ns->base.lock);
+ return profile;
+}
+
+/**
+ * aa_interface_add_profiles - Unpack and add new profile(s) to the profile list
+ * @data: serialized data stream
+ * @size: size of the serialized data stream
+ */
+ssize_t aa_interface_add_profiles(void *udata, size_t size)
+{
+ struct aa_profile *profile;
+ struct aa_namespace *ns = NULL;
+ struct aa_policy_common *common;
+ ssize_t error;
+ struct aa_audit_iface sa = {
+ .base.operation = "profile_load",
+ .base.gfp_mask = GFP_KERNEL,
+ };
+
+ /* check if loading policy is locked out */
+ if (aa_g_lock_policy)
+ return -EACCES;
+
+ /* released below */
+ profile = aa_unpack(udata, size, &sa);
+ if (IS_ERR(profile))
+ return PTR_ERR(profile);
+
+ /* released below */
+ ns = aa_prepare_namespace(sa.name2);
+ if (IS_ERR(ns)) {
+ sa.base.info = "failed to prepare namespace";
+ sa.base.error = PTR_ERR(ns);
+ goto fail;
+ }
+ /* profiles are currently loaded flat with fqnames */
+ sa.name = profile->fqname;
+
+ write_lock(&ns->base.lock);
+
+ /* no ref on common only use inside of lock */
+ common = __aa_find_parent(ns, sa.name);
+ if (!common) {
+ sa.base.info = "parent does not exist";
+ sa.base.error = -ENOENT;
+ goto fail2;
+ }
+
+ if (common != &ns->base)
+ /* released on profile replacement or free_aa_profile */
+ profile->parent = aa_get_profile((struct aa_profile *)common);
+
+ if (__aa_find_child(&common->profiles, profile->base.name)) {
+ /* A profile with this name exists already. */
+ sa.base.info = "profile already exists";
+ sa.base.error = -EEXIST;
+ goto fail2;
+ }
+ /* released on free_aa_profile */
+ profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ profile->ns = aa_get_namespace(ns);
+ __aa_add_profile(common, profile);
+ write_unlock(&ns->base.lock);
+
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(profile);
+ if (error)
+ return error;
+ return size;
+
+fail2:
+ write_unlock(&ns->base.lock);
+
+fail:
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(profile);
+ return error;
+}
+
+/**
+ * aa_interface_replace_profiles - replace profile(s) on the profile list
+ * @udata: serialized data stream
+ * @size: size of the serialized data stream
+ *
+ * unpack and replace a profile on the profile list and uses of that profile
+ * by any aa_task_context. If the profile does not exist on the profile list
+ * it is added. Return %0 or error.
+ */
+ssize_t aa_interface_replace_profiles(void *udata, size_t size)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *old_profile = NULL, *new_profile;
+ struct aa_namespace *ns;
+ ssize_t error;
+ struct aa_audit_iface sa = {
+ .base.operation = "profile_replace",
+ .base.gfp_mask = GFP_KERNEL,
+ };
+
+ /* check if loading policy is locked out */
+ if (aa_g_lock_policy)
+ return -EACCES;
+
+ /* released below */
+ new_profile = aa_unpack(udata, size, &sa);
+ if (IS_ERR(new_profile))
+ return PTR_ERR(new_profile);
+
+ /* released below */
+ ns = aa_prepare_namespace(sa.name2);
+ if (!ns) {
+ sa.base.info = "failed to prepare namespace";
+ sa.base.error = -ENOMEM;
+ goto fail;
+ }
+
+ sa.name = new_profile->fqname;
+
+ write_lock(&ns->base.lock);
+ /* no ref on common only use inside lock */
+ common = __aa_find_parent(ns, sa.name);
+
+ if (!common) {
+ sa.base.info = "parent does not exist";
+ sa.base.error = -ENOENT;
+ goto fail2;
+ }
+
+ old_profile = __aa_find_child(&common->profiles,
+ new_profile->base.name);
+ /* released below */
+ aa_get_profile(old_profile);
+ if (old_profile && old_profile->flags & PFLAG_IMMUTABLE) {
+ sa.base.info = "cannot replace immutible profile";
+ sa.base.error = -EPERM;
+ goto fail2;
+ } else if (old_profile) {
+ __aa_replace_profile(old_profile, new_profile);
+ } else {
+ __aa_add_profile(common, new_profile);
+ new_profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ }
+
+ write_unlock(&ns->base.lock);
+
+ if (!old_profile)
+ sa.base.operation = "profile_load";
+
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(old_profile);
+ aa_put_profile(new_profile);
+ if (error)
+ return error;
+ return size;
+
+fail2:
+ write_unlock(&ns->base.lock);
+fail:
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(old_profile);
+ aa_put_profile(new_profile);
+ return error;
+}
+
+/**
+ * aa_interface_remove_profiles - remove profile(s) from the system
+ * @name: name of the profile to remove
+ * @size: size of the name
+ *
+ * remove a profile from the profile list and all aa_task_context references
+ * to said profile.
+ * NOTE: removing confinement does not restore rlimits to preconfinemnet values
+ */
+ssize_t aa_interface_remove_profiles(char *name, size_t size)
+{
+ struct aa_namespace *ns;
+ struct aa_profile *profile;
+ struct aa_audit_iface sa = {
+ .base.operation = "profile_remove",
+ .base.gfp_mask = GFP_KERNEL,
+ };
+
+ /* check if loading policy is locked out */
+ if (aa_g_lock_policy)
+ return -EACCES;
+
+ write_lock(&ns_list_lock);
+ if (name[0] == ':') {
+ char *ns_name;
+ name = aa_split_name_from_ns(name, &ns_name);
+ /* released below */
+ ns = aa_get_namespace(__aa_find_namespace(&ns_list, ns_name));
+ } else {
+ /* released below */
+ ns = aa_get_namespace(default_namespace);
+ }
+
+ if (!ns) {
+ sa.base.info = "failed: namespace does not exist";
+ goto fail_ns_list_lock;
+ }
+
+ sa.name2 = ns->base.name;
+ write_lock(&ns->base.lock);
+ if (!name) {
+ /* remove namespace */
+ if (ns == default_namespace)
+ __aa_profile_list_release(&ns->base.profiles);
+ else
+ __aa_remove_namespace(ns);
+ } else {
+ /* released below - remove profile */
+ profile = aa_get_profile(__aa_find_profile(ns, name));
+ if (!profile) {
+ sa.name = name;
+ sa.base.info = "failed: profile does not exist";
+ goto fail_ns_lock;
+ }
+ sa.name = profile->fqname;
+ __aa_profile_list_release(&profile->base.profiles);
+ __aa_replace_profile(profile, NULL);
+ }
+ write_unlock(&ns->base.lock);
+ write_unlock(&ns_list_lock);
+
+ aa_audit_iface(&sa);
+ aa_put_profile(profile);
+ aa_put_namespace(ns);
+ return size;
+
+fail_ns_lock:
+ write_unlock(&ns->base.lock);
+
+fail_ns_list_lock:
+ write_unlock(&ns_list_lock);
+ aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ return -ENOENT;
+}
diff --git a/security/apparmor/sid.c b/security/apparmor/sid.c
new file mode 100644
index 0000000..e20843f
--- /dev/null
+++ b/security/apparmor/sid.c
@@ -0,0 +1,108 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor security identifier (sid) manipulation fns
+ *
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 allocates a unique sid for every profile loaded. If a profile
+ * is replaced it receive the sid of the profile it is replacing. Each sid
+ * is a u32 with the lower u16 being sids of system profiles and the
+ * upper u16 being user profile sids.
+ *
+ * The sid value of 0 is invalid for system sids and is used to indicate
+ * unconfined for user sids.
+ *
+ * A compound sid is a pair of user and system sids that is used to identify
+ * both profiles confining a task.
+ *
+ * Both system and user sids are globally unique with all users pulling
+ * from the same sid pool. User sid allocation is limited by the
+ * user controls, that can limit how many profiles are loaded by a user.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+
+#include "include/sid.h"
+
+/* global counter from which sids are allocated */
+static u16 global_sys_sid;
+static u16 global_usr_sid;
+static DEFINE_SPINLOCK(sid_lock);
+
+/* TODO FIXME: add sid to profile mapping, and sid recycling */
+
+/**
+ * aa_alloc_sid - allocate a new sid for a profile
+ * @is_usr: true if the new sid is a user based sid
+ */
+u32 aa_alloc_sid(int is_usr)
+{
+ u32 sid;
+
+ /*
+ * TODO FIXME: sid recycling - part of profile mapping table
+ */
+ spin_lock(&sid_lock);
+ if (is_usr)
+ sid = (++global_usr_sid) << 16;
+ else
+ sid = ++global_sys_sid;
+ spin_unlock(&sid_lock);
+ return sid;
+}
+
+/**
+ * aa_free_sid - free a sid
+ * @sid: sid to free
+ */
+void aa_free_sid(u32 sid)
+{
+ ; /* NOP ATM */
+}
+
+/**
+ * aa_add_sid_profile - associate a profile to a sid for sid -> profile lookup
+ * @sid: sid of te profile
+ * @profile: profile to associate
+ *
+ * return 0 or error
+ */
+int aa_add_sid_profile(u32 sid, struct aa_profile *profile)
+{
+ /* NOP ATM */
+ return 0;
+}
+
+/**
+ * aa_replace_sid_profile - replace the profile associated with a sid
+ * @sid: sid to associate a new profile with
+ * @profile: profile to associate with side
+ *
+ * return 0 or error
+ */
+int aa_replace_sid_profile(u32 sid, struct aa_profile *profile)
+{
+ /* NOP ATM */
+ return 0;
+}
+
+/**
+ * aa_get_sid_profile - get the profile associated with the sid
+ * @sid: sid to lookup
+ *
+ * returns - the profile, or NULL for unconfined user.
+ * - if there is an error -ENOENT, -EINVAL
+ */
+struct aa_profile *aa_get_sid_profile(u32 sid)
+{
+ return ERR_PTR(-EINVAL);
+}
--
1.6.3.3

2009-11-10 16:15:30

by John Johansen

[permalink] [raw]
Subject: [PATCH 05/12] AppArmor: dfa match engine

A basic dfa matching engine based off the dfa engine in the Dragon
Book. It uses simple row compression with a check field.

This allows AppArmor to do pattern matching in linear time, and also
avoids stack issues that an nfa based engine may have. The dfa
engine uses a byte based comparison, with all values being valid.
Any potential character encoding are handled user side when the dfa
tables are created. By convention AppArmor uses \0 to separate two
dependent path matches since \0 is not a valid path character
(this is done in the link permission check).

The dfa tables are generated in user space and are verified at load
time to be internally consistent.

There are several future improvements planned for the dfa engine:
* Currently AppArmor permissions are embedded in the accept table.
Inthe future AppArmor specific dependencies will be separated out,
making the dfa a generic matching engine.
* The dfa engine may be converted to a hybrid nfa-dfa engine, with
a fixed size limited stack. This would allow for size time
tradeoffs, by inserting limited nfa states to help control
state explosion that can occur with dfas.
* The dfa engine may pickup the ability to do limited dynamic
variable matching, instead of fixing all variables at policy
load time.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/match.h | 104 +++++++++++++
security/apparmor/match.c | 301 +++++++++++++++++++++++++++++++++++++
2 files changed, 405 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/match.h
create mode 100644 security/apparmor/match.c

diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
new file mode 100644
index 0000000..bd5015d
--- /dev/null
+++ b/security/apparmor/include/match.h
@@ -0,0 +1,104 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy dfa matching engine definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_MATCH_H
+#define __AA_MATCH_H
+
+#define DFA_NOMATCH 0
+#define DFA_START 1
+
+#define DFA_VALID_PERM_MASK 0xffffffff
+#define DFA_VALID_PERM2_MASK 0xffffffff
+
+/**
+ * The format used for transition tables is based on the GNU flex table
+ * file format (--tables-file option; see Table File Format in the flex
+ * info pages and the flex sources for documentation). The magic number
+ * used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because
+ * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
+ * slightly differently (see the apparmor-parser package).
+ */
+
+#define YYTH_MAGIC 0x1B5E783D
+
+struct table_set_header {
+ u32 th_magic; /* YYTH_MAGIC */
+ u32 th_hsize;
+ u32 th_ssize;
+ u16 th_flags;
+ char th_version[];
+};
+
+#define YYTD_ID_ACCEPT 1
+#define YYTD_ID_BASE 2
+#define YYTD_ID_CHK 3
+#define YYTD_ID_DEF 4
+#define YYTD_ID_EC 5
+#define YYTD_ID_META 6
+#define YYTD_ID_ACCEPT2 7
+#define YYTD_ID_NXT 8
+
+#define YYTD_DATA8 1
+#define YYTD_DATA16 2
+#define YYTD_DATA32 4
+
+struct table_header {
+ u16 td_id;
+ u16 td_flags;
+ u32 td_hilen;
+ u32 td_lolen;
+ char td_data[];
+};
+
+#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF - 1]->td_data))
+#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE - 1]->td_data))
+#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT - 1]->td_data))
+#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK - 1]->td_data))
+#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC - 1]->td_data))
+#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT - 1]->td_data))
+#define ACCEPT_TABLE2(DFA) ((u32 *)\
+ ((DFA)->tables[YYTD_ID_ACCEPT2 - 1]->td_data))
+
+struct aa_dfa {
+ struct table_header *tables[YYTD_ID_NXT];
+};
+
+#define byte_to_byte(X) (X)
+
+#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
+ do { \
+ typeof(LEN) __i; \
+ TYPE *__t = (TYPE *) TABLE; \
+ TYPE *__b = (TYPE *) BLOB; \
+ for (__i = 0; __i < LEN; __i++) { \
+ __t[__i] = NTOHX(__b[__i]); \
+ } \
+ } while (0)
+
+static inline size_t table_size(size_t len, size_t el_size)
+{
+ return ALIGN(sizeof(struct table_header) + len * el_size, 8);
+}
+
+struct aa_dfa *aa_match_alloc(void);
+void aa_match_free(struct aa_dfa *dfa);
+int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size);
+int verify_dfa(struct aa_dfa *dfa);
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len);
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str);
+unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start);
+
+#endif /* __AA_MATCH_H */
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
new file mode 100644
index 0000000..c76807b
--- /dev/null
+++ b/security/apparmor/match.c
@@ -0,0 +1,301 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor dfa based regular expression matching engine
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include "include/apparmor.h"
+#include "include/match.h"
+#include "include/file.h"
+
+static void free_table(struct table_header *table)
+{
+ if (is_vmalloc_addr(table))
+ vfree(table);
+ else
+ kfree(table);
+}
+
+static struct table_header *unpack_table(void *blob, size_t bsize)
+{
+ struct table_header *table = NULL;
+ struct table_header th;
+ size_t tsize;
+
+ if (bsize < sizeof(struct table_header))
+ goto out;
+
+ th.td_id = be16_to_cpu(*(u16 *) (blob));
+ th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
+ th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
+ blob += sizeof(struct table_header);
+
+ if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
+ th.td_flags == YYTD_DATA8))
+ goto out;
+
+ tsize = table_size(th.td_lolen, th.td_flags);
+ if (bsize < tsize)
+ goto out;
+
+ /* freed by free_table */
+ table = kmalloc(tsize, GFP_KERNEL);
+ if (!table)
+ table = vmalloc(tsize);
+ if (table) {
+ *table = th;
+ if (th.td_flags == YYTD_DATA8)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u8, byte_to_byte);
+ else if (th.td_flags == YYTD_DATA16)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u16, be16_to_cpu);
+ else
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u32, be32_to_cpu);
+ }
+
+out:
+ return table;
+}
+
+int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
+{
+ int hsize, i;
+ int error = -ENOMEM;
+
+ /* get dfa table set header */
+ if (size < sizeof(struct table_set_header))
+ goto fail;
+
+ if (ntohl(*(u32 *) blob) != YYTH_MAGIC)
+ goto fail;
+
+ hsize = ntohl(*(u32 *) (blob + 4));
+ if (size < hsize)
+ goto fail;
+
+ blob += hsize;
+ size -= hsize;
+
+ error = -EPROTO;
+ while (size > 0) {
+ struct table_header *table;
+ table = unpack_table(blob, size);
+ if (!table)
+ goto fail;
+
+ switch (table->td_id) {
+ case YYTD_ID_ACCEPT:
+ case YYTD_ID_ACCEPT2:
+ case YYTD_ID_BASE:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA32)
+ goto fail;
+ break;
+ case YYTD_ID_DEF:
+ case YYTD_ID_NXT:
+ case YYTD_ID_CHK:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA16)
+ goto fail;
+ break;
+ case YYTD_ID_EC:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA8)
+ goto fail;
+ break;
+ default:
+ free_table(table);
+ goto fail;
+ }
+
+ blob += table_size(table->td_lolen, table->td_flags);
+ size -= table_size(table->td_lolen, table->td_flags);
+ }
+
+ return 0;
+
+fail:
+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
+ free_table(dfa->tables[i]);
+ dfa->tables[i] = NULL;
+ }
+ return error;
+}
+
+/**
+ * verify_dfa - verify that all the transitions and states in the dfa tables
+ * are in bounds.
+ * @dfa: dfa to test
+ *
+ * assumes dfa has gone through the verification done by unpacking
+ */
+int verify_dfa(struct aa_dfa *dfa)
+{
+ size_t i, state_count, trans_count;
+ int error = -EPROTO;
+
+ /* check that required tables exist */
+ if (!(dfa->tables[YYTD_ID_ACCEPT - 1] &&
+ dfa->tables[YYTD_ID_ACCEPT2 - 1] &&
+ dfa->tables[YYTD_ID_DEF - 1] &&
+ dfa->tables[YYTD_ID_BASE - 1] &&
+ dfa->tables[YYTD_ID_NXT - 1] && dfa->tables[YYTD_ID_CHK - 1]))
+ goto out;
+
+ /* accept.size == default.size == base.size */
+ state_count = dfa->tables[YYTD_ID_BASE - 1]->td_lolen;
+ if (!(state_count == dfa->tables[YYTD_ID_DEF - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT2 - 1]->td_lolen))
+ goto out;
+
+ /* next.size == chk.size */
+ trans_count = dfa->tables[YYTD_ID_NXT - 1]->td_lolen;
+ if (trans_count != dfa->tables[YYTD_ID_CHK - 1]->td_lolen)
+ goto out;
+
+ /* if equivalence classes then its table size must be 256 */
+ if (dfa->tables[YYTD_ID_EC - 1] &&
+ dfa->tables[YYTD_ID_EC - 1]->td_lolen != 256)
+ goto out;
+
+ for (i = 0; i < state_count; i++) {
+ if (DEFAULT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (BASE_TABLE(dfa)[i] >= trans_count + 256)
+ goto out;
+ }
+
+ for (i = 0; i < trans_count; i++) {
+ if (NEXT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (CHECK_TABLE(dfa)[i] >= state_count)
+ goto out;
+ }
+
+ /* verify accept permissions */
+ for (i = 0; i < state_count; i++) {
+ int mode = ACCEPT_TABLE(dfa)[i];
+
+ if (mode & ~DFA_VALID_PERM_MASK)
+ goto out;
+ if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)
+ goto out;
+
+ }
+
+ error = 0;
+out:
+ return error;
+}
+
+struct aa_dfa *aa_match_alloc(void)
+{
+ /* freed by aa_match_free, by caller */
+ return kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
+}
+
+void aa_match_free(struct aa_dfa *dfa)
+{
+ if (dfa) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++)
+ free_table(dfa->tables[i]);
+ }
+ kfree(dfa);
+}
+
+/**
+ * aa_dfa_match_len - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
+ * @start: the state of the dfa to start matching in
+ * @str: the string of bytes to match against the dfa
+ * @len: length of the string of bytes to match
+ *
+ * aa_dfa_match_len will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ *
+ * This function will happily match again the 0 byte and only finishes
+ * when @len input is consumed.
+ */
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len)
+{
+ u16 *def = DEFAULT_TABLE(dfa);
+ u32 *base = BASE_TABLE(dfa);
+ u16 *next = NEXT_TABLE(dfa);
+ u16 *check = CHECK_TABLE(dfa);
+ unsigned int state = start, pos;
+
+ if (state == 0)
+ return 0;
+
+ /* current state is <state>, matching character *str */
+ if (dfa->tables[YYTD_ID_EC - 1]) {
+ u8 *equiv = EQUIV_TABLE(dfa);
+ for (; len; len--) {
+ pos = base[state] + equiv[(u8) *str++];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ } else {
+ for (; len; len--) {
+ pos = base[state] + (u8) *str++;
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ }
+ return state;
+}
+
+/**
+ * aa_dfa_next_state - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
+ * @start: the state of the dfa to start matching in
+ * @str: the null terminated string of bytes to match against the dfa
+ *
+ * aa_dfa_next_state will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ */
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str)
+{
+ return aa_dfa_match_len(dfa, start, str, strlen(str));
+}
+
+/**
+ * aa_dfa_null_transition - step to next state after null character
+ * @dfa: the dfa to match against
+ * @start: the state of the dfa to start matching in
+ *
+ * aa_dfa_null_transition transitions to the next state after a null
+ * character which is not used in standard matching and is only
+ * used to seperate pairs.
+ */
+unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start)
+{
+ return aa_dfa_match_len(dfa, start, "", 1);
+}
--
1.6.3.3

2009-11-10 16:13:36

by John Johansen

[permalink] [raw]
Subject: [PATCH 06/12] AppArmor: policy routines for loading and unpacking policy

AppArmor policy is loaded in a platform independent flattened binary
stream. Verify and unpack the data converting it to the internal
format needed for enforcement.

The current routines are compatible with older AppArmor policy. There
are several planned improvements, to reduce the amount of unpacking
and unaligned accesses being done by moving to more aligned tables
that can be shared similar to what is done currently with dfa tables.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/policy_unpack.h | 30 ++
security/apparmor/policy_unpack.c | 602 +++++++++++++++++++++++++++++
2 files changed, 632 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/policy_unpack.h
create mode 100644 security/apparmor/policy_unpack.c

diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
new file mode 100644
index 0000000..1c02ce6
--- /dev/null
+++ b/security/apparmor/include/policy_unpack.h
@@ -0,0 +1,30 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy loading interface function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __POLICY_INTERFACE_H
+#define __POLICY_INTERFACE_H
+
+struct aa_audit_iface {
+ struct aa_audit base;
+
+ const char *name;
+ const char *name2;
+ long pos;
+};
+
+int aa_audit_iface(struct aa_audit_iface *sa);
+struct aa_profile *aa_unpack(void *udata, size_t size,
+ struct aa_audit_iface *sa);
+
+#endif /* __POLICY_INTERFACE_H */
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
new file mode 100644
index 0000000..9b64f21
--- /dev/null
+++ b/security/apparmor/policy_unpack.c
@@ -0,0 +1,602 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions for unpacking policy loaded from
+ * userspace.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 uses a serialized binary format for loading policy.
+ * The policy format is documented in Documentation/???
+ * All policy is validated all before it is used.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/errno.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/match.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+#include "include/sid.h"
+
+/*
+ * The AppArmor interface treats data as a type byte followed by the
+ * actual data. The interface has the notion of a a named entry
+ * which has a name (AA_NAME typecode followed by name string) followed by
+ * the entries typecode and data. Named types allow for optional
+ * elements and extensions to be added and tested for without breaking
+ * backwards compatability.
+ */
+
+enum aa_code {
+ AA_U8,
+ AA_U16,
+ AA_U32,
+ AA_U64,
+ AA_NAME, /* same as string except it is items name */
+ AA_STRING,
+ AA_BLOB,
+ AA_STRUCT,
+ AA_STRUCTEND,
+ AA_LIST,
+ AA_LISTEND,
+ AA_ARRAY,
+ AA_ARRAYEND,
+};
+
+/*
+ * aa_ext is the read of the buffer containing the serialized profile. The
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
+ * the unpack routines.
+ */
+struct aa_ext {
+ void *start;
+ void *end;
+ void *pos; /* pointer to current position in the buffer */
+ u32 version;
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_iface *sa = va;
+
+ if (sa->name)
+ audit_log_format(ab, " name=%s", sa->name);
+ if (sa->name2)
+ audit_log_format(ab, " namespace=%s", sa->name2);
+ if (sa->base.error && sa->pos)
+ audit_log_format(ab, " offset=%ld", sa->pos);
+}
+
+int aa_audit_iface(struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile;
+ struct cred *cred = aa_get_task_policy(current, &profile);
+ int error = aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa->base,
+ audit_cb);
+ put_cred(cred);
+ return error;
+}
+
+static int aa_inbounds(struct aa_ext *e, size_t size)
+{
+ return (size <= e->end - e->pos);
+}
+
+/**
+ * aa_u16_chunck - test and do bounds checking for a u16 size based chunk
+ * @e: serialized data read head
+ * @chunk: start address for chunk of data
+ *
+ * return the size of chunk found with the read head at the end of
+ * the chunk.
+ */
+static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
+{
+ size_t size = 0;
+
+ if (!aa_inbounds(e, sizeof(u16)))
+ return 0;
+ size = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ if (!aa_inbounds(e, size))
+ return 0;
+ *chunk = e->pos;
+ e->pos += size;
+ return size;
+}
+
+static int unpack_X(struct aa_ext *e, enum aa_code code)
+{
+ if (!aa_inbounds(e, 1))
+ return 0;
+ if (*(u8 *) e->pos != code)
+ return 0;
+ e->pos++;
+ return 1;
+}
+
+/**
+ * unpack_nameX - check is the next element is of type X with a name of @name
+ * @e: serialized data extent information
+ * @code: type code
+ * @name: name to match to the serialized element.
+ *
+ * check that the next serialized data element is of type X and has a tag
+ * name @name. If @name is specified then there must be a matching
+ * name element in the stream. If @name is NULL any name element will be
+ * skipped and only the typecode will be tested.
+ * returns 1 on success (both type code and name tests match) and the read
+ * head is advanced past the headers
+ * returns %0 if either match failes, the read head does not move
+ */
+static int unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
+{
+ /*
+ * May need to rest pos if name or type doesn't match, could be
+ * an optional component.
+ */
+ void *pos = e->pos;
+ /*
+ * Check for presence of a tagname, and if present name size
+ * AA_NAME tag value is a u16.
+ */
+ if (unpack_X(e, AA_NAME)) {
+ char *tag = NULL;
+ size_t size = unpack_u16_chunk(e, &tag);
+ /* if a name is specified it must match. otherwise skip tag */
+ if (name && (!size || strcmp(name, tag)))
+ goto fail;
+ } else if (name) {
+ /* if a name is specified and there is no name tag fail */
+ goto fail;
+ }
+
+ /* now check if type code matches */
+ if (unpack_X(e, code))
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U16, name)) {
+ if (!aa_inbounds(e, sizeof(u16)))
+ return 0;
+ if (data)
+ *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return 1;
+ }
+ return 0;
+}
+
+static int unpack_u32(struct aa_ext *e, u32 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U32, name)) {
+ if (!aa_inbounds(e, sizeof(u32)))
+ return 0;
+ if (data)
+ *data = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ return 1;
+ }
+ return 0;
+}
+
+static int unpack_u64(struct aa_ext *e, u64 *data, const char *name)
+{
+ if (unpack_nameX(e, AA_U64, name)) {
+ if (!aa_inbounds(e, sizeof(u64)))
+ return 0;
+ if (data)
+ *data = le64_to_cpu(get_unaligned((u64 *) e->pos));
+ e->pos += sizeof(u64);
+ return 1;
+ }
+ return 0;
+}
+
+static size_t unpack_array(struct aa_ext *e, const char *name)
+{
+ if (unpack_nameX(e, AA_ARRAY, name)) {
+ int size;
+ if (!aa_inbounds(e, sizeof(u16)))
+ return 0;
+ size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return size;
+ }
+ return 0;
+}
+
+static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
+{
+ if (unpack_nameX(e, AA_BLOB, name)) {
+ u32 size;
+ if (!aa_inbounds(e, sizeof(u32)))
+ return 0;
+ size = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ if (aa_inbounds(e, (size_t) size)) {
+ *blob = e->pos;
+ e->pos += size;
+ return size;
+ }
+ }
+ return 0;
+}
+
+static int unpack_string(struct aa_ext *e, char **string, const char *name)
+{
+ char *src_str;
+ size_t size = 0;
+ void *pos = e->pos;
+ *string = NULL;
+ if (unpack_nameX(e, AA_STRING, name)) {
+ size = unpack_u16_chunk(e, &src_str);
+ if (size) {
+ /* strings are null terminated, length is size - 1 */
+ if (src_str[size - 1] != 0)
+ goto fail;
+ *string = src_str;
+ }
+ }
+ return size;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_dynstring(struct aa_ext *e, char **string, const char *name)
+{
+ char *tmp;
+ void *pos = e->pos;
+ int res = unpack_string(e, &tmp, name);
+ *string = NULL;
+
+ if (!res)
+ return 0;
+
+ *string = kstrdup(tmp, GFP_KERNEL);
+ if (!*string) {
+ e->pos = pos;
+ return 0;
+ }
+
+ return res;
+}
+
+/**
+ * aa_unpack_dfa - unpack a file rule dfa
+ * @e: serialized data extent information
+ *
+ * returns dfa or ERR_PTR
+ */
+static struct aa_dfa *aa_unpack_dfa(struct aa_ext *e)
+{
+ char *blob = NULL;
+ size_t size, error = 0;
+ struct aa_dfa *dfa = NULL;
+
+ size = unpack_blob(e, &blob, "aadfa");
+ if (size) {
+ dfa = aa_match_alloc();
+ if (dfa) {
+ /*
+ * The dfa is aligned with in the blob to 8 bytes
+ * from the beginning of the stream.
+ */
+ size_t sz = blob - (char *)e->start;
+ size_t pad = ALIGN(sz, 8) - sz;
+ error = unpack_dfa(dfa, blob + pad, size - pad);
+ if (!error)
+ error = verify_dfa(dfa);
+ } else {
+ error = -ENOMEM;
+ }
+
+ if (error) {
+ aa_match_free(dfa);
+ dfa = ERR_PTR(error);
+ }
+ }
+
+ return dfa;
+}
+
+static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* exec table is optional */
+ if (unpack_nameX(e, AA_STRUCT, "xtable")) {
+ int i, size;
+
+ size = unpack_array(e, NULL);
+ /* currently 4 exec bits and entries 0-3 are reserved iupcx */
+ if (size > 16 - 4)
+ goto fail;
+ profile->file.trans.table = kzalloc(sizeof(char *) * size,
+ GFP_KERNEL);
+ if (!profile->file.trans.table)
+ goto fail;
+
+ profile->file.trans.size = size;
+ for (i = 0; i < size; i++) {
+ char *tmp;
+ if (!unpack_dynstring(e, &tmp, NULL))
+ goto fail;
+ /*
+ * note: strings beginning with a : have an embedded
+ * \0 seperating the profile ns name from the profile
+ * name
+ */
+ profile->file.trans.table[i] = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ if (profile->file.trans.table) {
+ int i;
+ for (i = 0; i < profile->file.trans.size; i++)
+ kfree(profile->file.trans.table[i]);
+ }
+ e->pos = pos;
+ return 0;
+}
+
+int aa_unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* rlimits are optional */
+ if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
+ int i, size;
+ u32 tmp = 0;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.mask = tmp;
+
+ size = unpack_array(e, NULL);
+ if (size > RLIM_NLIMITS)
+ goto fail;
+ for (i = 0; i < size; i++) {
+ u64 tmp = 0;
+ if (!unpack_u64(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.limits[i].rlim_max = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/**
+ * aa_unpack_profile - unpack a serialized profile
+ * @e: serialized data extent information
+ * @sa: audit struct for the operation
+ */
+static struct aa_profile *aa_unpack_profile(struct aa_ext *e,
+ struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile = NULL;
+ char *name;
+ size_t size = 0;
+ int i, error = -EPROTO;
+ u32 tmp;
+ u64 tmp64;
+
+ /* check that we have the right struct being passed */
+ if (!unpack_nameX(e, AA_STRUCT, "profile"))
+ goto fail;
+ if (!unpack_string(e, &name, NULL))
+ goto fail;
+
+ profile = alloc_aa_profile(name);
+ if (!profile)
+ return ERR_PTR(-ENOMEM);
+
+ /* xmatch is optional and may be NULL */
+ profile->xmatch = aa_unpack_dfa(e);
+ if (IS_ERR(profile->xmatch)) {
+ error = PTR_ERR(profile->xmatch);
+ profile->xmatch = NULL;
+ goto fail;
+ }
+ /* xmatch_len is not optional is xmatch is set */
+ if (profile->xmatch && !unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->xmatch_len = tmp;
+
+ /* per profile debug flags (complain, audit) */
+ if (!unpack_nameX(e, AA_STRUCT, "flags"))
+ goto fail;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->flags |= PFLAG_HAT;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->mode = APPARMOR_COMPLAIN;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->audit = AUDIT_ALL;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ /* mmap_min_addr is optional */
+ if (unpack_u64(e, &tmp64, "mmap_min_addr")) {
+ profile->mmap_min_addr = (unsigned long)tmp64;
+ if (((u64) profile->mmap_min_addr) == tmp64) {
+ profile->flags |= PFLAG_MMAP_MIN_ADDR;
+ } else {
+ sa->base.info = "invalid set mmap_min_addr";
+ goto fail;
+ }
+ }
+
+ if (!unpack_u32(e, &(profile->caps.allowed.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.set.cap[0]), NULL))
+ goto fail;
+
+ if (unpack_nameX(e, AA_STRUCT, "caps64")) {
+ /* optional upper half of 64 bit caps */
+ if (!unpack_u32(e, &(profile->caps.allowed.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.set.cap[1]), NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+
+ if (!aa_unpack_rlimits(e, profile))
+ goto fail;
+
+ size = unpack_array(e, "net_allowed_af");
+ if (size) {
+ if (size > AF_MAX)
+ goto fail;
+
+ for (i = 0; i < size; i++) {
+ if (!unpack_u16(e, &profile->net.allowed[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.audit[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ /*
+ * allow unix domain and netlink sockets they are handled
+ * by IPC
+ */
+ }
+ profile->net.allowed[AF_UNIX] = 0xffff;
+ profile->net.allowed[AF_NETLINK] = 0xffff;
+
+ /* get file rules */
+ profile->file.dfa = aa_unpack_dfa(e);
+ if (IS_ERR(profile->file.dfa)) {
+ error = PTR_ERR(profile->file.dfa);
+ profile->file.dfa = NULL;
+ goto fail;
+ }
+
+ if (!aa_unpack_trans_table(e, profile))
+ goto fail;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ return profile;
+
+fail:
+ sa->name = profile && profile->base.name ? profile->base.name :
+ "unknown";
+ if (!sa->base.info)
+ sa->base.info = "failed to unpack profile";
+ aa_audit_iface(sa);
+
+ free_aa_profile(profile);
+
+ return ERR_PTR(error);
+}
+
+/**
+ * aa_verify_head - unpack serialized stream header
+ * @e: serialized data read head
+ * @operation: operation header is being verified for
+ *
+ * returns error or 0 if header is good
+ */
+static int aa_verify_header(struct aa_ext *e, struct aa_audit_iface *sa)
+{
+ /* get the interface version */
+ if (!unpack_u32(e, &e->version, "version")) {
+ sa->base.info = "invalid profile format";
+ aa_audit_iface(sa);
+ return -EPROTONOSUPPORT;
+ }
+
+ /* check that the interface version is currently supported */
+ if (e->version != 5) {
+ sa->base.info = "unsupported interface version";
+ aa_audit_iface(sa);
+ return -EPROTONOSUPPORT;
+ }
+
+ /* read the namespace if present */
+ if (!unpack_string(e, &sa->name2, "namespace"))
+ sa->name2 = NULL;
+
+ return 0;
+}
+
+/**
+ * aa_unpack - unpack packed binary profile data loaded from user space
+ * @udata: user data copied to kmem
+ * @size: the size of the user data
+ * @sa: audit struct for unpacking
+ *
+ * Unpack user data and return refcounted allocated profile or ERR_PTR
+ */
+struct aa_profile *aa_unpack(void *udata, size_t size,
+ struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile;
+ int error;
+ struct aa_ext e = {
+ .start = udata,
+ .end = udata + size,
+ .pos = udata,
+ };
+
+ error = aa_verify_header(&e, sa);
+ if (error)
+ return ERR_PTR(error);
+
+ profile = aa_unpack_profile(&e, sa);
+ if (IS_ERR(profile))
+ sa->pos = e.pos - e.start;
+ /* return refcount */
+ return profile;
+}
--
1.6.3.3

2009-11-10 16:13:40

by John Johansen

[permalink] [raw]
Subject: [PATCH 07/12] AppArmor: userspace interfaces

AppArmor /proc/<pid>/attr/* and apparmorfs interfaces to userspace.

The /proc/<pid>/attr/* interface is used for process introspection and
commands. While the apparmorfs interface is used for global introspection
and loading and removing policy.

The current apparmorfs interface is compatible with previous versions
of AppArmor. The plans are to deprecate it (hence the config option
APPARMOR_COMPAT_24) and replace it with a more sysfs style single
entry per file interface.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/apparmorfs-24.c | 211 ++++++++++++++++++++++++++
security/apparmor/apparmorfs.c | 260 ++++++++++++++++++++++++++++++++
security/apparmor/include/apparmorfs.h | 29 ++++
security/apparmor/include/procattr.h | 26 +++
security/apparmor/procattr.c | 114 ++++++++++++++
5 files changed, 640 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/apparmorfs-24.c
create mode 100644 security/apparmor/apparmorfs.c
create mode 100644 security/apparmor/include/apparmorfs.h
create mode 100644 security/apparmor/include/procattr.h
create mode 100644 security/apparmor/procattr.c

diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c
new file mode 100644
index 0000000..02a3396
--- /dev/null
+++ b/security/apparmor/apparmorfs-24.c
@@ -0,0 +1,211 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ *
+ *
+ * This file contain functions providing an interface for <= AppArmor 2.4
+ * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24
+ * being set (see Makefile).
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.h"
+
+/**
+ * next_profile - step to the next profile to be output
+ * @profile: profile that was last output
+ *
+ * Perform a depth first traversal over profile tree.
+ *
+ * Returns: next profile or NULL if done
+ * Requires: ns_list_lock, and profile->ns->base.lock be held
+ * will unlock profile->ns.base.lock and aquire lock for next ns
+ * __releases(last ns lock);
+ */
+static struct aa_profile *next_profile(struct aa_profile *profile)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = profile->ns;
+
+ /* is next profile a child */
+ if (!list_empty(&profile->base.profiles))
+ return list_first_entry(&profile->base.profiles,
+ struct aa_profile, base.list);
+
+ /* is next a sibling, parent sibling, gp sibling */
+ parent = profile->parent;
+ while (parent) {
+ list_for_each_entry_continue(profile, &parent->base.profiles,
+ base.list)
+ return profile;
+ profile = parent;
+ parent = parent->parent;
+ }
+
+ /* is next the another profile in the namespace */
+ list_for_each_entry_continue(profile, &ns->base.profiles, base.list)
+ return profile;
+
+ /* finished all profiles in namespace move to next namespace */
+ read_unlock(&ns->base.lock);
+ list_for_each_entry_continue(ns, &ns_list, base.list) {
+ read_lock(&ns->base.lock);
+ return list_first_entry(&ns->base.profiles, struct aa_profile,
+ base.list);
+ }
+
+ /* done all profiles */
+ return NULL;
+}
+
+/**
+ * p_start - start a depth first traversal of profile tree
+ * @f: seq_file to fill
+ * @pos: current position
+ *
+ * acquires first ns->base.lock
+ */
+static void *p_start(struct seq_file *f, loff_t *pos) __acquires(ns_list_lock)
+{
+ struct aa_namespace *ns;
+ loff_t l = *pos;
+
+ read_lock(&ns_list_lock);
+ if (!list_empty(&ns_list)) {
+ struct aa_profile *profile = NULL;
+ ns = list_first_entry(&ns_list, typeof(*ns), base.list);
+ read_lock(&ns->base.lock);
+ if (!list_empty(&ns->base.profiles)) {
+ profile = list_first_entry(&ns->base.profiles,
+ typeof(*profile), base.list);
+ /* skip to position */
+ for (; profile && l > 0; l--)
+ profile = next_profile(profile);
+ return profile;
+ } else
+ read_unlock(&ns->base.lock);
+ }
+ return NULL;
+}
+
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+
+ (*pos)++;
+ profile = next_profile(profile);
+
+ return profile;
+}
+
+/**
+ * p_stop - stop depth first traversal
+ * @f: seq_file we are filling
+ * @p: the last profile writen
+ *
+ * if we haven't completely traversed the profile tree will release the
+ * ns->base.lock, if we have the ns->base.lock was released in next_profile
+ */
+static void p_stop(struct seq_file *f, void *p) __releases(ns_list_lock)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+
+ if (profile)
+ read_unlock(&profile->ns->base.lock);
+ read_unlock(&ns_list_lock);
+}
+
+static void print_name(struct seq_file *f, struct aa_profile *profile)
+{
+ if (profile->parent) {
+ print_name(f, profile->parent);
+ seq_printf(f, "//");
+ }
+ seq_printf(f, "%s", profile->base.name);
+}
+
+static int seq_show_profile(struct seq_file *f, void *p)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+
+ if (profile->ns != default_namespace)
+ seq_printf(f, ":%s:", profile->ns->base.name);
+ print_name(f, profile);
+ seq_printf(f, " (%s)\n",
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
+
+ return 0;
+}
+
+/* Used in apparmorfs.c */
+static const struct seq_operations aa_fs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
+
+static int aa_profiles_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &aa_fs_profiles_op);
+}
+
+static int aa_profiles_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+const struct file_operations aa_fs_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 audit perms=crwxamlk/ "
+ "user::other";
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ sizeof(matching) - 1);
+}
+
+const struct file_operations aa_fs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+/* apparmor/features */
+static ssize_t aa_features_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char features[] = "file=3.1 capability=2.0 network=1.0 "
+ "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1";
+
+ return simple_read_from_buffer(buf, size, ppos, features,
+ sizeof(features) - 1);
+}
+
+const struct file_operations aa_fs_features_fops = {
+ .read = aa_features_read,
+};
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
new file mode 100644
index 0000000..8aa1c48
--- /dev/null
+++ b/security/apparmor/apparmorfs.c
@@ -0,0 +1,260 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/security/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/apparmorfs.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.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)
+{
+ const struct cred *cred;
+ 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.
+ */
+ cred = aa_current_policy(&profile);
+ if (profile) {
+ struct aa_audit sa = {
+ .operation = operation,
+ .gfp_mask = GFP_KERNEL,
+ .error = -EACCES,
+ };
+ data = ERR_PTR(aa_audit(AUDIT_APPARMOR_DENIED, profile, &sa,
+ NULL));
+ goto out;
+ }
+
+ /* freed by caller to aa_simple_write_to_buffer */
+ 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/.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_interface_add_profiles(data, size);
+ vfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_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_interface_replace_profiles(data, size);
+ vfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_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_interface_remove_profiles(data, size);
+ vfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_profile_remove = {
+ .write = aa_profile_remove
+};
+
+static struct dentry *aa_fs_dentry;
+struct dentry *aa_fs_null;
+struct vfsmount *aa_fs_mnt;
+
+static void aafs_remove(const char *name)
+{
+ struct dentry *dentry;
+
+ dentry = lookup_one_len(name, aa_fs_dentry, strlen(name));
+ if (!IS_ERR(dentry)) {
+ securityfs_remove(dentry);
+ dput(dentry);
+ }
+}
+
+/**
+ * aafs_create - create an entry in the apparmor filesystem
+ * @name: name of the entry
+ * @mask: file permission mask of the file
+ * @fops: file operations for the file
+ *
+ * Used aafs_remove to remove entries created with this fn.
+ */
+static int aafs_create(const char *name, int mask,
+ const struct file_operations *fops)
+{
+ struct dentry *dentry;
+
+ dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry,
+ NULL, fops);
+
+ return IS_ERR(dentry) ? PTR_ERR(dentry) : 0;
+}
+
+/**
+ * aa_destroy_aafs - cleanup and free aafs
+ *
+ * releases dentries allocated by aa_create_aafs
+ */
+void aa_destroy_aafs(void)
+{
+ if (aa_fs_dentry) {
+ aafs_remove(".remove");
+ aafs_remove(".replace");
+ aafs_remove(".load");
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ aafs_remove("matching");
+ aafs_remove("features");
+ aafs_remove("profiles");
+#endif
+ securityfs_remove(aa_fs_dentry);
+ aa_fs_dentry = NULL;
+ }
+}
+
+/**
+ * aa_create_aafs - create the apparmor security filesystem
+ *
+ * dentries created here are released by aa_destroy_aafs
+ */
+int aa_create_aafs(void)
+{
+ int error;
+
+ if (!apparmor_initialized)
+ return 0;
+
+ if (aa_fs_dentry) {
+ AA_ERROR("%s: AppArmor securityfs already exists\n", __func__);
+ return -EEXIST;
+ }
+
+ aa_fs_dentry = securityfs_create_dir("apparmor", NULL);
+ if (IS_ERR(aa_fs_dentry)) {
+ error = PTR_ERR(aa_fs_dentry);
+ aa_fs_dentry = NULL;
+ goto error;
+ }
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ error = aafs_create("profiles", 0440, &aa_fs_profiles_fops);
+ if (error)
+ goto error;
+ error = aafs_create("matching", 0444, &aa_fs_matching_fops);
+ if (error)
+ goto error;
+ error = aafs_create("features", 0444, &aa_fs_features_fops);
+ if (error)
+ goto error;
+#endif
+ error = aafs_create(".load", 0640, &aa_fs_profile_load);
+ if (error)
+ goto error;
+ error = aafs_create(".replace", 0640, &aa_fs_profile_replace);
+ if (error)
+ goto error;
+ error = aafs_create(".remove", 0640, &aa_fs_profile_remove);
+ if (error)
+ goto error;
+
+ /* TODO: add support for apparmorfs_null and apparmorfs_mnt */
+
+ /* Report that AppArmor fs is enabled */
+ aa_info_message("AppArmor Filesystem Enabled");
+ return 0;
+
+error:
+ aa_destroy_aafs();
+ AA_ERROR("Error creating AppArmor securityfs\n");
+ apparmor_disable();
+ return error;
+}
+
+fs_initcall(aa_create_aafs);
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
new file mode 100644
index 0000000..177b35b
--- /dev/null
+++ b/security/apparmor/include/apparmorfs.h
@@ -0,0 +1,29 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor filesystem definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_APPARMORFS_H
+#define __AA_APPARMORFS_H
+
+extern struct dentry *aa_fs_null;
+extern struct vfsmount *aa_fs_mnt;
+
+extern void aa_destroy_aafs(void);
+
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+extern const struct file_operations aa_fs_profiles_fops;
+extern const struct file_operations aa_fs_matching_fops;
+extern const struct file_operations aa_fs_features_fops;
+#endif
+
+#endif /* __AA_APPARMORFS_H */
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
new file mode 100644
index 0000000..70f4c8a
--- /dev/null
+++ b/security/apparmor/include/procattr.h
@@ -0,0 +1,26 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /proc/<pid>/attr/ interface function defintions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_PROCATTR_H
+#define __AA_PROCATTR_H
+
+#define AA_DO_TEST 1
+
+int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile,
+ char **string);
+int aa_setprocattr_changehat(char *args, int test);
+int aa_setprocattr_changeprofile(char *args, int onexec, int test);
+int aa_setprocattr_permipc(char *args);
+
+#endif /* __AA_PROCATTR_H */
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
new file mode 100644
index 0000000..cba407a
--- /dev/null
+++ b/security/apparmor/procattr.c
@@ -0,0 +1,114 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /proc/<pid>/attr/ interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include "include/apparmor.h"
+#include "include/policy.h"
+#include "include/domain.h"
+
+int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile,
+ char **string)
+{
+ char *str;
+ int len = 0;
+
+ if (profile) {
+ int mode_len, name_len, ns_len = 0;
+ const char *mode_str = profile_mode_names[profile->mode];
+ char *s;
+
+ mode_len = strlen(mode_str) + 3; /* + 3 for _() */
+ name_len = strlen(profile->fqname);
+ if (ns != default_namespace)
+ ns_len = strlen(ns->base.name) + 3; /*+ 3 for :// */
+ len = mode_len + ns_len + name_len + 1; /*+ 1 for \n */
+ s = str = kmalloc(len + 1, GFP_ATOMIC); /* + 1 \0 */
+ if (!str)
+ return -ENOMEM;
+
+ if (ns_len) {
+ sprintf(s, "%s://", ns->base.name);
+ s += ns_len;
+ }
+ sprintf(s, "%s (%s)\n",profile->fqname, mode_str);
+ } else {
+ const char unconfined_str[] = "unconfined\n";
+
+ len = sizeof(unconfined_str) - 1; /* - 1 for \0 */
+ if (ns != default_namespace)
+ len += strlen(ns->base.name) + 3; /* + 3 for :// */
+
+ str = kmalloc(len + 1, GFP_ATOMIC);
+ if (!str)
+ return -ENOMEM;
+
+ if (ns != default_namespace)
+ sprintf(str, "%s://%s", ns->base.name, unconfined_str);
+ else
+ memcpy(str, unconfined_str, len);
+ }
+ *string = str;
+
+ /* NOTE: len does not include \0 of string, not saved as part of file */
+ return len;
+}
+
+static char *split_token_from_name(const char *op, char *args, u64 * token)
+{
+ char *name;
+
+ *token = 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, int test)
+{
+ char *hat;
+ u64 token;
+
+ hat = split_token_from_name("change_hat", args, &token);
+ if (IS_ERR(hat))
+ return PTR_ERR(hat);
+
+ if (!hat && !token) {
+ AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
+ return -EINVAL;
+ }
+
+ AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
+ __func__, token, hat ? hat : NULL);
+
+ return aa_change_hat(hat, token, test);
+}
+
+int aa_setprocattr_changeprofile(char *args, int onexec, int test)
+{
+ char *name, *ns_name;
+
+ name = aa_split_name_from_ns(args, &ns_name);
+ return aa_change_profile(ns_name, name, onexec, test);
+}
+
+int aa_setprocattr_permipc(char *args)
+{
+ /* TODO: add ipc permission querying */
+ return -ENOTSUPP;
+}
--
1.6.3.3

2009-11-10 16:14:53

by John Johansen

[permalink] [raw]
Subject: [PATCH 08/12] AppArmor: file enforcement routines

AppArmor does files enforcement via pathname matching. Matching is done
at file open using a dfa match engine. Permission is against the final
file object not parent directories, ie. the traversal of directories
as part of the file match is implicitly allowed. In the case of nonexistant
files (creation) permissions are checked against the target file not the
directory. eg. In case of creating the file /dir/new, permissions are
checked against the match /dir/new not against /dir/.

The permissions for matches are currently stored in the dfa accept table,
but this will change to allow for dfa reuse and also to allow for sharing
of wider accept states.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/file.c | 423 ++++++++++++++++++++++++++++++++++++++
security/apparmor/include/file.h | 225 ++++++++++++++++++++
2 files changed, 648 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/file.c
create mode 100644 security/apparmor/include/file.h

diff --git a/security/apparmor/file.c b/security/apparmor/file.c
new file mode 100644
index 0000000..9591836
--- /dev/null
+++ b/security/apparmor/file.c
@@ -0,0 +1,423 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor mediation of files
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/file.h"
+#include "include/match.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+struct file_perms nullperms;
+
+static void aa_audit_file_sub_mask(struct audit_buffer *ab, char *buffer,
+ u16 mask, u16 xindex)
+{
+ char *m = buffer;
+
+ if (mask & AA_EXEC_MMAP)
+ *m++ = 'm';
+ if (mask & MAY_READ)
+ *m++ = 'r';
+ if (mask & (MAY_WRITE | AA_MAY_CREATE))
+ *m++ = 'w';
+ else if (mask & MAY_APPEND)
+ *m++ = 'a';
+ if (mask & AA_MAY_LINK)
+ *m++ = 'l';
+ if (mask & AA_MAY_LOCK)
+ *m++ = 'k';
+ if (mask & MAY_EXEC)
+ *m++ = 'x';
+ *m++ = '\0';
+}
+
+static void aa_audit_file_mask(struct audit_buffer *ab, const char *name,
+ u16 mask, int xindex, int owner)
+{
+ char str[10];
+
+ aa_audit_file_sub_mask(ab, str, mask, xindex);
+ if (owner)
+ audit_log_format(ab, " %s=\"%s::\"", name, str);
+ else
+ audit_log_format(ab, " %s=\"::%s\"", name, str);
+}
+
+void file_audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_file *sa = va;
+ u16 denied = sa->request & ~sa->perms.allowed;
+ uid_t fsuid;
+
+ if (sa->base.task)
+ fsuid = task_uid(sa->base.task);
+ else
+ fsuid = current_fsuid();
+
+ if (sa->request & AA_AUDIT_FILE_MASK)
+ aa_audit_file_mask(ab, "requested_mask", sa->request,
+ AA_X_NONE, fsuid == sa->cond->uid);
+
+ if (denied & AA_AUDIT_FILE_MASK)
+ aa_audit_file_mask(ab, "denied_mask", denied, sa->perms.xindex,
+ fsuid == sa->cond->uid);
+
+ if (sa->request & AA_AUDIT_FILE_MASK) {
+ audit_log_format(ab, " fsuid=%d", fsuid);
+ audit_log_format(ab, " ouid=%d", sa->cond->uid);
+ }
+
+ if (sa->name) {
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, sa->name);
+ }
+
+ if (sa->name2) {
+ audit_log_format(ab, " name2=");
+ audit_log_untrustedstring(ab, sa->name2);
+ }
+
+ if (sa->name3) {
+ audit_log_format(ab, " name3=");
+ audit_log_untrustedstring(ab, sa->name3);
+ }
+}
+
+/**
+ * aa_audit_file - handle the auditing of file operations
+ * @profile: the profile being enforced
+ * @sa: file auditing context
+ */
+int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa)
+{
+ int type = AUDIT_APPARMOR_AUTO;
+
+ if (likely(!sa->base.error)) {
+ u16 mask = sa->perms.audit;
+
+ if (unlikely(PROFILE_AUDIT_MODE(profile) == AUDIT_ALL))
+ mask = 0xffff;
+
+ /* mask off perms that are not being force audited */
+ sa->request &= mask;
+
+ if (likely(!sa->request))
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ /* only report permissions that were denied */
+ sa->request = sa->request & ~sa->perms.allowed;
+
+ if (sa->request & sa->perms.kill)
+ type = AUDIT_APPARMOR_KILL;
+
+ /* quiet known rejects, assumes quiet and kill do not overlap */
+ if ((sa->request & sa->perms.quiet) &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_ALL)
+ sa->request &= ~sa->perms.quiet;
+
+ if (!sa->request)
+ return PROFILE_COMPLAIN(profile) ? 0 : sa->base.error;
+ }
+ return aa_audit(type, profile, (struct aa_audit *)sa, file_audit_cb);
+}
+
+/* TODO: convert from dfa + state to permission entry */
+struct file_perms aa_compute_perms(struct aa_dfa *dfa, unsigned int state,
+ struct path_cond *cond)
+{
+ struct file_perms perms;
+
+ /* FIXME: change over to new dfa format
+ * currently file perms are encoded in the dfa, new format
+ * splits the permissions from the dfa. This mapping can be
+ * done at profile load
+ */
+ perms.kill = 0;
+ perms.dindex = 0;
+
+ if (current_fsuid() == cond->uid) {
+ perms.allowed = dfa_user_allow(dfa, state);
+ perms.audit = dfa_user_audit(dfa, state);
+ perms.quiet = dfa_user_quiet(dfa, state);
+ perms.xindex = dfa_user_xindex(dfa, state);
+ } else {
+ perms.allowed = dfa_other_allow(dfa, state);
+ perms.audit = dfa_other_audit(dfa, state);
+ perms.quiet = dfa_other_quiet(dfa, state);
+ perms.xindex = dfa_other_xindex(dfa, state);
+ }
+ /* in the old mapping MAY_WRITE implies AA_MAY_CREATE */
+ perms.allowed |= (perms.allowed & MAY_WRITE) << 6;
+ perms.audit |= (perms.audit & MAY_WRITE) << 6;
+ perms.quiet |= (perms.quiet & MAY_WRITE) << 6;
+
+ /* in the old mapping AA_MAY_LOCK and link subset are overlayed
+ * and only determined by which part of a pair they are in
+ */
+ if (perms.allowed & AA_MAY_LOCK)
+ perms.allowed |= AA_LINK_SUBSET;
+
+ /* change_profile wasn't determined by ownership in old mapping */
+ if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
+ perms.allowed |= AA_MAY_CHANGE_PROFILE;
+
+ return perms;
+}
+
+struct file_perms aa_str_perms(struct aa_dfa *dfa, unsigned int start,
+ const char *name, struct path_cond *cond,
+ unsigned int *rstate)
+{
+ unsigned int state;
+ if (!dfa)
+ return nullperms;
+
+ state = aa_dfa_match(dfa, start, name);
+
+ if (rstate)
+ *rstate = state;
+
+ /* TODO: convert to new dfa format */
+
+ return aa_compute_perms(dfa, state, cond);
+}
+
+int aa_pathstr_perm(struct aa_profile *profile, const char *op,
+ const char *name, u16 request, struct path_cond *cond)
+{
+ struct aa_audit_file sa = {
+ .base.operation = op,
+ .base.gfp_mask = GFP_KERNEL,
+ .request = request,
+ .name = name,
+ .cond = cond,
+ };
+
+ sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, cond,
+ NULL);
+ if (request & ~sa.perms.allowed)
+ sa.base.error = -EACCES;
+ return aa_audit_file(profile, &sa);
+}
+
+int aa_path_perm(struct aa_profile *profile, const char *operation,
+ struct path *path, u16 request, struct path_cond *cond)
+{
+ char *buffer, *name;
+ struct aa_audit_file sa = {
+ .base.operation = operation,
+ .base.gfp_mask = GFP_KERNEL,
+ .request = request,
+ .cond = cond,
+ };
+
+ /* buffer freed below - name is pointer inside buffer */
+ sa.base.error = aa_get_name(path, S_ISDIR(cond->mode), &buffer, &name);
+ sa.name = name;
+ if (sa.base.error) {
+ sa.perms = nullperms;
+ if (sa.base.error == -ENOENT)
+ sa.base.info = "Failed name lookup - deleted entry";
+ else if (sa.base.error == -ESTALE)
+ sa.base.info = "Failed name lookup - disconnected path";
+ else if (sa.base.error == -ENAMETOOLONG)
+ sa.base.info = "Failed name lookup - name too long";
+ else
+ sa.base.info = "Failed name lookup";
+ } else {
+ sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name,
+ cond, NULL);
+ if (request & ~sa.perms.allowed)
+ sa.base.error = -EACCES;
+ }
+ sa.base.error = aa_audit_file(profile, &sa);
+ kfree(buffer);
+
+ return sa.base.error;
+}
+
+/**
+ * aa_path_link - Handle hard link permission check
+ * @profile: the profile being enforced
+ * @old_dentry: the target dentry
+ * @new_dir: directory the new link will be created in
+ * @new_dentry: the link being created
+ *
+ * Handle the permission test for a link & target pair. Permission
+ * is encoded as a pair where the link permission is determined
+ * first, and if allowed, the target is tested. The target test
+ * is done from the point of the link match (not start of DFA)
+ * making the target permission dependent on the link permission match.
+ *
+ * The subset test if required forces that permissions granted
+ * on link are a subset of the permission granted to target.
+ */
+int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry)
+{
+ struct path link = { new_dir->mnt, new_dentry };
+ struct path target = { new_dir->mnt, old_dentry };
+ struct path_cond cond = {
+ old_dentry->d_inode->i_uid,
+ old_dentry->d_inode->i_mode
+ };
+ char *buffer = NULL, *buffer2 = NULL;
+ char *lname, *tname;
+ struct file_perms perms;
+ unsigned int state;
+
+ struct aa_audit_file sa = {
+ .base.operation = "link",
+ .base.gfp_mask = GFP_KERNEL,
+ .request = AA_MAY_LINK,
+ .cond = &cond,
+ .perms = nullperms,
+ };
+ /* buffer freed below, lname is pointer in buffer */
+ sa.base.error = aa_get_name(&link, 0, &buffer, &lname);
+ sa.name = lname;
+ if (sa.base.error)
+ goto audit;
+
+ /* buffer2 freed below, tname is pointer in buffer2 */
+ sa.base.error = aa_get_name(&target, 0, &buffer2, &tname);
+ sa.name2 = tname;
+ if (sa.base.error)
+ goto audit;
+
+ sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, &cond,
+ &state);
+ sa.perms.audit &= AA_MAY_LINK;
+ sa.perms.quiet &= AA_MAY_LINK;
+ sa.perms.kill &= AA_MAY_LINK;
+
+ if (!(sa.perms.allowed & AA_MAY_LINK)) {
+ sa.base.error = -EACCES;
+ goto audit;
+ }
+
+ /* test to see if target can be paired with link */
+ state = aa_dfa_null_transition(profile->file.dfa, state);
+ perms = aa_str_perms(profile->file.dfa, state, sa.name2, &cond, NULL);
+ if (!(perms.allowed & AA_MAY_LINK)) {
+ sa.base.error = -EACCES;
+ sa.base.info = "target restricted";
+ goto audit;
+ }
+
+ /* done if link subset test is not required */
+ if (!(perms.allowed & AA_LINK_SUBSET))
+ goto audit;
+
+ /* Do link perm subset test requiring allowed permission on link are a
+ * subset of the allowed permissions on target.
+ */
+ perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name2, &cond,
+ NULL);
+
+ /* AA_MAY_LINK is not considered in the subset test */
+ sa.request = sa.perms.allowed & ~AA_MAY_LINK;
+ sa.perms.allowed &= perms.allowed | AA_MAY_LINK;
+
+ sa.request |= AA_AUDIT_FILE_MASK & (sa.perms.allowed & ~perms.allowed);
+ if (sa.request & ~sa.perms.allowed)
+ sa.base.error = -EACCES;
+ else if (sa.perms.allowed & MAY_EXEC) {
+ if (((sa.perms.xindex & ~AA_X_UNSAFE) !=
+ (perms.xindex & ~AA_X_UNSAFE)) ||
+ ((sa.perms.xindex & AA_X_UNSAFE) &&
+ !(perms.xindex & AA_X_UNSAFE))) {
+ sa.perms.allowed &= ~MAY_EXEC;
+ sa.request |= MAY_EXEC;
+ sa.base.error = -EACCES;
+ sa.base.info = "link not subset of target";
+ }
+ }
+
+audit:
+ sa.base.error = aa_audit_file(profile, &sa);
+ kfree(buffer);
+ kfree(buffer2);
+
+ return sa.base.error;
+}
+
+static inline int aa_is_deleted_file(struct dentry *dentry)
+{
+ if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
+ return 1;
+ return 0;
+}
+
+int aa_file_common_perm(struct aa_profile *profile, const char *operation,
+ struct file *file, u16 request, const char *name,
+ int error)
+{
+ struct path_cond cond = {
+ .uid = file->f_path.dentry->d_inode->i_uid,
+ .mode = file->f_path.dentry->d_inode->i_mode
+ };
+ struct aa_audit_file sa = {
+ .base.operation = operation,
+ .base.gfp_mask = GFP_KERNEL,
+ .request = request,
+ .base.error = error,
+ .name = name,
+ .cond = &cond,
+ };
+
+ if (sa.base.error) {
+ sa.perms = nullperms;
+ if (sa.base.error == -ENOENT &&
+ aa_is_deleted_file(file->f_path.dentry)) {
+ /* Access to open files that are deleted are
+ * give a pass (implicit delegation
+ */
+ sa.base.error = 0;
+ sa.perms.allowed = sa.request;
+ } else if (sa.base.error == -ENOENT)
+ sa.base.info = "Failed name lookup - deleted entry";
+ else if (sa.base.error == -ESTALE)
+ sa.base.info = "Failed name lookup - disconnected path";
+ else if (sa.base.error == -ENAMETOOLONG)
+ sa.base.info = "Failed name lookup - name too long";
+ else
+ sa.base.info = "Failed name lookup";
+ } else {
+ sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name,
+ &cond, NULL);
+ if (request & ~sa.perms.allowed)
+ sa.base.error = -EACCES;
+ }
+ sa.base.error = aa_audit_file(profile, &sa);
+
+ return sa.base.error;
+}
+
+int aa_file_perm(struct aa_profile *profile, const char *operation,
+ struct file *file, u16 request)
+{
+ char *buffer, *name;
+ umode_t mode = file->f_path.dentry->d_inode->i_mode;
+ /* buffer freed below, name is a pointer inside of buffer */
+ int error = aa_get_name(&file->f_path, S_ISDIR(mode), &buffer, &name);
+
+ error = aa_file_common_perm(profile, operation, file, request, name,
+ error);
+ kfree(buffer);
+ return error;
+}
diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h
new file mode 100644
index 0000000..8913f5a
--- /dev/null
+++ b/security/apparmor/include/file.h
@@ -0,0 +1,225 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor file mediation function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_FILE_H
+#define __AA_FILE_H
+
+#include <linux/path.h>
+
+#include "audit.h"
+#include "domain.h"
+#include "match.h"
+
+struct aa_profile;
+
+/*
+ * We use MAY_EXEC, MAY_WRITE, MAY_READ, MAY_APPEND and the following flags
+ * for profile permissions
+ */
+#define AA_MAY_LINK 0x0010
+#define AA_MAY_LOCK 0x0020
+#define AA_EXEC_MMAP 0x0040
+
+#define AA_MAY_CREATE 0x0080
+#define AA_LINK_SUBSET 0x0100
+#define AA_MAY_DELEGATE 0x0200
+#define AA_EXEC_DELEGATE 0x0400 /*exec allows delegate */
+
+#define AA_MAY_CHANGEHAT 0x2000 /* ctrl auditing only */
+#define AA_MAY_ONEXEC 0x4000 /* exec allows onexec */
+#define AA_MAY_CHANGE_PROFILE 0x8000
+
+#define AA_AUDIT_FILE_MASK (MAY_READ | MAY_WRITE | MAY_EXEC | MAY_APPEND |\
+ AA_MAY_LINK | AA_MAY_LOCK | AA_EXEC_MMAP | \
+ AA_MAY_CREATE)
+
+/*
+ * The xindex is broken into 3 parts
+ * - index - an index into either the exec name table or the variable table
+ * - exec type - which determines how the executable name and index are used
+ * - flags - which modify how the destination name is applied
+ */
+#define AA_X_INDEX_MASK 0x03ff
+
+#define AA_X_TYPE_MASK 0x0c00
+#define AA_X_TYPE_SHIFT 10
+#define AA_X_NONE 0x0000
+#define AA_X_NAME 0x0400 /* use executable name px */
+#define AA_X_TABLE 0x0800 /* use a specified name ->n# */
+
+#define AA_X_UNSAFE 0x1000
+#define AA_X_CHILD 0x2000 /* make >AA_X_NONE apply to children */
+#define AA_X_INHERIT 0x4000
+#define AA_X_UNCONFINED 0x8000
+
+/* AA_SECURE_X_NEEDED - is passed in the bprm->unsafe field */
+#define AA_SECURE_X_NEEDED 0x8000
+
+/* need to conditionalize which ones are being set */
+struct path_cond {
+ uid_t uid;
+ umode_t mode;
+};
+
+/* struct file_perms - file permission fo
+ * @allowed: mask of permissions that are allowed
+ * @audit: mask of permissions to force an audit message for
+ * @quiet: mask of permissions to quiet audit messages for
+ * @kill: mask of permissions that when matched will kill the task
+ * @xindex: exec transition index if @allowed contains MAY_EXEC
+ * @dindex: delegate table index if @allowed contain AA_MAY_DELEGATE
+ *
+ * The @audit and @queit mask should be mutually exclusive.
+ */
+struct file_perms {
+ u16 allowed;
+ u16 audit;
+ u16 quiet;
+ u16 kill;
+ u16 xindex;
+ u16 dindex;
+};
+
+extern struct file_perms nullperms;
+
+#define COMBINED_PERM_MASK(X) ((X).allowed | (X).audit | (X).quiet | (X).kill)
+
+/* FIXME: split perms from dfa and match this to description
+ * also add delegation info.
+ */
+static inline u16 dfa_map_xindex(u16 mask)
+{
+ u16 old_index = (mask >> 10) & 0xf;
+ u16 index = 0;
+
+ if (mask & 0x100)
+ index |= AA_X_UNSAFE;
+ if (mask & 0x200)
+ index |= AA_X_INHERIT;
+ if (mask & 0x80)
+ index |= AA_X_UNCONFINED;
+
+ if (old_index == 1) {
+ index |= AA_X_UNCONFINED;
+ } else if (old_index == 2) {
+ index |= AA_X_NAME;
+ } else if (old_index == 3) {
+ index |= AA_X_NAME | AA_X_CHILD;
+ } else {
+ index |= AA_X_TABLE;
+ index |= old_index - 4;
+ }
+
+ return index;
+}
+
+/*
+ * map old dfa inline permissions to new format
+ */
+#define dfa_user_allow(dfa, state) ((ACCEPT_TABLE(dfa)[state]) & 0x7f)
+#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
+#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
+#define dfa_user_xindex(dfa, state) \
+ (dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))
+
+#define dfa_other_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 14) & 0x7f)
+#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
+#define dfa_other_quiet(dfa, state) \
+ ((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
+#define dfa_other_xindex(dfa, state) \
+ dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)
+
+struct aa_audit_file {
+ struct aa_audit base;
+
+ const char *name;
+ const char *name2;
+ const char *name3;
+ struct file_perms perms;
+ u16 request;
+ struct path_cond *cond;
+};
+
+int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa);
+void file_audit_cb(struct audit_buffer *ab, void *va);
+
+/**
+ * struct aa_file_rules - components used for file rule permissions
+ * @dfa: dfa to match path names and conditionals against
+ * @perms: permission table indexed by the matched state accept entry of @dfa
+ * @trans: transition table for indexed by named x transitions
+ *
+ * File permission are determined by matching a path against @dfa and then
+ * then using the value of the accept entry for the matching state as
+ * an index into @perms. If a named exec transition is required it is
+ * looked up in the transition table.
+ */
+struct aa_file_rules {
+ struct aa_dfa *dfa;
+ /* struct perms perms; */
+ struct aa_domain trans;
+ /* TODO: add delegate table */
+};
+
+struct file_perms aa_str_perms(struct aa_dfa *dfa, unsigned int start,
+ const char *name, struct path_cond *cond,
+ unsigned int *rstate);
+
+int aa_pathstr_perm(struct aa_profile *profile, const char *op,
+ const char *name, u16 request, struct path_cond *cond);
+
+int aa_path_perm(struct aa_profile *profile, const char *operation,
+ struct path *path, u16 request, struct path_cond *cond);
+
+int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry);
+
+int aa_file_common_perm(struct aa_profile *profile, const char *operation,
+ struct file *file, u16 request, const char *name,
+ int error);
+
+int aa_file_perm(struct aa_profile *profile, const char *operation,
+ struct file *file, u16 request);
+
+static inline void aa_free_file_rules(struct aa_file_rules *rules)
+{
+ aa_match_free(rules->dfa);
+ aa_free_domain_entries(&rules->trans);
+}
+
+#define ACC_FMODE(x) (("\000\004\002\006"[(x)&O_ACCMODE]) | (((x) << 1) & 0x40))
+
+/* from namei.c */
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+#define MAP_OPEN_FLAGS(x) ((((x) + 1) & O_ACCMODE) ? (x) + 1 : (x))
+/*
+ * map file flags to AppArmor permissions
+ */
+static inline u16 aa_map_file_to_perms(struct file *file)
+{
+ int flags = MAP_OPEN_FLAGS(file->f_flags);
+ u16 perms = ACC_FMODE(file->f_mode);
+
+ if ((flags & O_APPEND) && (perms & MAY_WRITE))
+ perms = (perms & ~MAY_WRITE) | MAY_APPEND;
+ /* trunc implies write permission */
+ if (flags & O_TRUNC)
+ perms |= MAY_WRITE;
+ if (flags & O_CREAT)
+ perms |= AA_MAY_CREATE;
+
+ return perms;
+}
+
+#endif /* __AA_FILE_H */
--
1.6.3.3

2009-11-10 16:13:51

by John Johansen

[permalink] [raw]
Subject: [PATCH 09/12] AppArmor: mediation of non file objects

ipc:
AppArmor ipc is currently limited to mediation done by file mediation
and basic ptrace tests. Improved mediation is a wip.

rlimits:
AppArmor provides basic abilities to set and control rlimits at
a per profile level. Only resources specified in a profile are controled
or set. AppArmor rules set the hard limit to a value <= to the current
hard limit (ie. they can not currently raise hard limits), and if
necessary will lower the soft limit to the new hard limit value.

AppArmor does not track resource limits to reset them when a profile
is left so that children processes inherit the limits set by the
parent even if they are not confined by the same profile.

net:
AppArmor net mediation is currently a per profile basic net toggle, at
the protocol level, ie. tcp, udp, raw.

More advanced network rules are a wip.

Capabilities: AppArmor provides a per profile mask of capabilities,
that will further restrict.

AppArmor provides the ability to raise capabilities on a per profile
basis. This is done by adding the set keyword to a capability rule.

set capability sys_admin,

This can be used to provide a function similar to fscaps but is
more intended for use with profiles attached through pam_apparmor,
and confined users.

The semantics of an AppArmor profile granting a capability is that
it is only granted while the task is confined by the profile, as
soon as the task transitions to a new profile or becomes unconfined
it looses the granted capability.

The current implementation does not set the task capabilities masks.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/capability.c | 122 +++++++++++++++++++++++++++
security/apparmor/include/capability.h | 45 ++++++++++
security/apparmor/include/ipc.h | 28 ++++++
security/apparmor/include/net.h | 40 +++++++++
security/apparmor/include/resource.h | 45 ++++++++++
security/apparmor/ipc.c | 107 +++++++++++++++++++++++
security/apparmor/net.c | 145 ++++++++++++++++++++++++++++++++
security/apparmor/resource.c | 103 ++++++++++++++++++++++
8 files changed, 635 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/capability.c
create mode 100644 security/apparmor/include/capability.h
create mode 100644 security/apparmor/include/ipc.h
create mode 100644 security/apparmor/include/net.h
create mode 100644 security/apparmor/include/resource.h
create mode 100644 security/apparmor/ipc.c
create mode 100644 security/apparmor/net.c
create mode 100644 security/apparmor/resource.c

diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
new file mode 100644
index 0000000..bb218fd
--- /dev/null
+++ b/security/apparmor/capability.c
@@ -0,0 +1,122 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor capability mediation functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/capability.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+
+#include "include/apparmor.h"
+#include "include/capability.h"
+#include "include/context.h"
+#include "include/policy.h"
+#include "include/audit.h"
+
+/*
+ * Table of capability names: we generate it from capabilities.h.
+ */
+#include "capability_names.h"
+
+struct audit_cache {
+ struct task_struct *task;
+ kernel_cap_t caps;
+};
+
+static DEFINE_PER_CPU(struct audit_cache, audit_cache);
+
+struct aa_audit_caps {
+ struct aa_audit base;
+
+ int cap;
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_caps *sa = va;
+
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, capability_names[sa->cap]);
+}
+
+static int aa_audit_caps(struct aa_profile *profile, struct aa_audit_caps *sa)
+{
+ struct audit_cache *ent;
+ int type = AUDIT_APPARMOR_AUTO;
+
+ if (likely(!sa->base.error)) {
+ /* test if auditing is being forced */
+ if (likely((PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !cap_raised(profile->caps.audit, sa->cap)))
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else if (PROFILE_KILL(profile) ||
+ cap_raised(profile->caps.kill, sa->cap)) {
+ type = AUDIT_APPARMOR_KILL;
+ } else if (cap_raised(profile->caps.quiet, sa->cap) &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) {
+ /* quiet auditing */
+ return sa->base.error;
+ }
+
+ /* Do simple duplicate message elimination */
+ ent = &get_cpu_var(audit_cache);
+ if (sa->base.task == ent->task && cap_raised(ent->caps, sa->cap)) {
+ put_cpu_var(audit_cache);
+ if (PROFILE_COMPLAIN(profile))
+ return 0;
+ return sa->base.error;
+ } else {
+ ent->task = sa->base.task;
+ cap_raise(ent->caps, sa->cap);
+ }
+ put_cpu_var(audit_cache);
+
+ return aa_audit(type, profile, &sa->base, audit_cb);
+}
+
+int aa_profile_capable(struct aa_profile *profile, int cap)
+{
+ return cap_raised(profile->caps.allowed, cap) ? 0 : -EPERM;
+}
+
+/**
+ * aa_capable - test permission to use capability
+ * @task: task doing capability test against
+ * @profile: profile confining @task
+ * @cap: capability to be tested
+ * @audit: whether an audit record should be generated
+ *
+ * Look up capability in profile capability set.
+ * Returns 0 on success, or else an error code.
+ */
+int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
+ int audit)
+{
+ int error = aa_profile_capable(profile, cap);
+ struct aa_audit_caps sa = {
+ .base.operation = "capable",
+ .base.task = task,
+ .base.gfp_mask = GFP_ATOMIC,
+ .base.error = error,
+ .cap = cap,
+ };
+
+ if (!audit) {
+ if (PROFILE_COMPLAIN(profile))
+ return 0;
+ return error;
+ }
+
+ return aa_audit_caps(profile, &sa);
+}
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
new file mode 100644
index 0000000..1343ee9
--- /dev/null
+++ b/security/apparmor/include/capability.h
@@ -0,0 +1,45 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor capability mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_CAPABILITY_H
+#define __AA_CAPABILITY_H
+
+#include <linux/sched.h>
+
+struct aa_profile;
+
+/* aa_caps - confinement data for capabilities
+ * @set_caps: capabilities that are being set
+ * @capabilities: capabilities mask
+ * @audit_caps: caps that are to be audited
+ * @quiet_caps: caps that should not be audited
+ */
+struct aa_caps {
+ kernel_cap_t set;
+ kernel_cap_t allowed;
+ kernel_cap_t audit;
+ kernel_cap_t quiet;
+ kernel_cap_t kill;
+};
+
+int aa_profile_capable(struct aa_profile *profile, int cap);
+int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
+ int audit);
+
+static inline void aa_free_cap_rules(struct aa_caps *caps)
+{
+ /* NOP */
+}
+
+#endif /* __AA_CAPBILITY_H */
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h
new file mode 100644
index 0000000..4fe96d3
--- /dev/null
+++ b/security/apparmor/include/ipc.h
@@ -0,0 +1,28 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor ipc mediation function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_IPC_H
+#define __AA_IPC_H
+
+#include <linux/sched.h>
+
+struct aa_profile;
+
+int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
+ struct aa_profile *tracee, unsigned int mode);
+
+int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
+ unsigned int mode);
+
+#endif /* __AA_IPC_H */
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..a3f6d05
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,40 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_NET_H
+#define __AA_NET_H
+
+#include <net/sock.h>
+
+/* struct aa_net - network confinement data
+ * @allowed: basic network families permissions
+ * @audit_network: which network permissions to force audit
+ * @quiet_network: which network permissions to quiet rejects
+ */
+struct aa_net {
+ u16 allowed[AF_MAX];
+ u16 audit[AF_MAX];
+ u16 quiet[AF_MAX];
+};
+
+extern int aa_net_perm(struct aa_profile *profile, char *operation,
+ int family, int type, int protocol);
+extern int aa_revalidate_sk(struct sock *sk, char *operation);
+
+static inline void aa_free_net_rules(struct aa_net *new)
+{
+ /* NOP */
+}
+
+#endif /* __AA_NET_H */
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h
new file mode 100644
index 0000000..4281041
--- /dev/null
+++ b/security/apparmor/include/resource.h
@@ -0,0 +1,45 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor resource limits function defintions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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 __AA_RESOURCE_H
+#define __AA_RESOURCE_H
+
+#include <linux/resource.h>
+#include <linux/sched.h>
+
+struct aa_profile;
+
+/* struct aa_rlimit - rlimits settings for the profile
+ * @mask: which hard limits to set
+ * @limits: rlimit values that override task limits
+ *
+ * AppArmor rlimits are used to set confined task rlimits. Only the
+ * limits specified in @mask will be controlled by apparmor.
+ */
+struct aa_rlimit {
+ unsigned int mask;
+ struct rlimit limits[RLIM_NLIMITS];
+};
+
+int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim);
+
+void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
+
+static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
+{
+ /* NOP */
+}
+
+#endif /* __AA_RESOURCE_H */
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c
new file mode 100644
index 0000000..d3ae53a
--- /dev/null
+++ b/security/apparmor/ipc.c
@@ -0,0 +1,107 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor ipc mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/gfp.h>
+#include <linux/ptrace.h>
+
+#include "include/audit.h"
+#include "include/capability.h"
+#include "include/context.h"
+#include "include/policy.h"
+
+struct aa_audit_ptrace {
+ struct aa_audit base;
+
+ pid_t tracer, tracee;
+};
+
+/* call back to audit ptrace fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_ptrace *sa = va;
+ audit_log_format(ab, " tracer=%d tracee=%d", sa->tracer, sa->tracee);
+}
+
+static int aa_audit_ptrace(struct aa_profile *profile,
+ struct aa_audit_ptrace *sa)
+{
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
+ audit_cb);
+}
+
+int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
+ struct aa_profile *tracee, unsigned int mode)
+{
+ /* TODO: currently only based on capability, not extended ptrace
+ * rules,
+ * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
+ */
+
+ if (!tracer || tracer == tracee)
+ return 0;
+ /* log this capability request */
+ return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1);
+}
+
+int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
+ unsigned int mode)
+{
+ /*
+ * tracer can ptrace tracee when
+ * - tracer is unconfined ||
+ * - tracer & tracee are in the same namespace &&
+ * - tracer is in complain mode
+ * - tracer has rules allowing it to trace tracee currently this is:
+ * - confined by the same profile ||
+ * - tracer profile has CAP_SYS_PTRACE
+ */
+
+ struct aa_profile *tracer_p;
+ /* cred released below */
+ const struct cred *cred = aa_get_task_policy(tracer, &tracer_p);
+ int error = 0;
+
+ if (tracer_p) {
+ struct aa_audit_ptrace sa = {
+ .base.operation = "ptrace",
+ .base.gfp_mask = GFP_ATOMIC,
+ .tracer = tracer->pid,
+ .tracee = tracee->pid,
+ };
+ /* FIXME: different namespace restriction can be lifted
+ * if, namespace are matched to AppArmor namespaces
+ */
+ if (tracer->nsproxy != tracee->nsproxy) {
+ sa.base.info = "different namespaces";
+ sa.base.error = -EPERM;
+ aa_audit(AUDIT_APPARMOR_DENIED, tracer_p, &sa.base,
+ audit_cb);
+ } else {
+ struct aa_profile *tracee_p;
+ /* lcred released below */
+ struct cred *lcred = aa_get_task_policy(tracee,
+ &tracee_p);
+
+ sa.base.error = aa_may_ptrace(tracer, tracer_p,
+ tracee_p, mode);
+ sa.base.error = aa_audit_ptrace(tracer_p, &sa);
+
+ put_cred(lcred);
+ }
+ error = sa.base.error;
+ }
+ put_cred(cred);
+
+ return error;
+}
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..254d52c
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,145 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor network mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/net.h"
+#include "include/policy.h"
+
+#include "af_names.h"
+
+static const char *sock_type_names[] = {
+ "unknown(0)",
+ "stream",
+ "dgram",
+ "raw",
+ "rdm",
+ "seqpacket",
+ "dccp",
+ "unknown(7)",
+ "unknown(8)",
+ "unknown(9)",
+ "packet",
+};
+
+struct aa_audit_net {
+ struct aa_audit base;
+
+ int family, type, protocol;
+
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_net *sa = va;
+
+ if (sa->family || sa->type) {
+ if (address_family_names[sa->family])
+ audit_log_format(ab, " family=\"%s\"",
+ address_family_names[sa->family]);
+ else
+ audit_log_format(ab, " family=\"unknown(%d)\"",
+ sa->family);
+
+ if (sock_type_names[sa->type])
+ audit_log_format(ab, " sock_type=\"%s\"",
+ sock_type_names[sa->type]);
+ else
+ audit_log_format(ab, " sock_type=\"unknown(%d)\"",
+ sa->type);
+
+ audit_log_format(ab, " protocol=%d", sa->protocol);
+ }
+
+}
+
+static int aa_audit_net(struct aa_profile *profile, struct aa_audit_net *sa)
+{
+ int type = AUDIT_APPARMOR_AUTO;
+
+ if (likely(!sa->base.error)) {
+ u16 audit_mask = profile->net.audit[sa->family];
+ if (likely((PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !(1 << sa->type & audit_mask)))
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ u16 quiet_mask = profile->net.quiet[sa->family];
+ u16 kill_mask = 0;
+ u16 denied = (1 << sa->type) & ~quiet_mask;
+
+ if (denied & kill_mask)
+ type = AUDIT_APPARMOR_KILL;
+
+ if ((denied & quiet_mask) &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_ALL)
+ return PROFILE_COMPLAIN(profile) ? 0 : sa->base.error;
+ }
+
+ return aa_audit(type, profile, (struct aa_audit *)sa, audit_cb);
+}
+
+int aa_net_perm(struct aa_profile *profile, char *operation,
+ int family, int type, int protocol)
+{
+ u16 family_mask;
+ struct aa_audit_net sa = {
+ .base.operation = operation,
+ .base.gfp_mask = GFP_KERNEL,
+ .family = family,
+ .type = type,
+ .protocol = protocol,
+ };
+
+ if ((family < 0) || (family >= AF_MAX))
+ return -EINVAL;
+
+ if ((type < 0) || (type >= SOCK_MAX))
+ return -EINVAL;
+
+ /* unix domain and netlink sockets are handled by ipc */
+ if (family == AF_UNIX || family == AF_NETLINK)
+ return 0;
+
+ family_mask = profile->net.allowed[family];
+
+ sa.base.error = (family_mask & (1 << type)) ? 0 : -EACCES;
+
+ return aa_audit_net(profile, &sa);
+}
+
+int aa_revalidate_sk(struct sock *sk, char *operation)
+{
+ struct aa_profile *profile;
+ struct cred *cred;
+ int error = 0;
+
+ /* aa_revalidate_sk should not be called from interrupt context
+ * don't mediate these calls as they are not task related
+ */
+ if (in_interrupt())
+ return 0;
+
+ /* cred released below */
+ cred = aa_get_task_policy(current, &profile);
+ if (profile)
+ error = aa_net_perm(profile, operation,
+ sk->sk_family, sk->sk_type,
+ sk->sk_protocol);
+ put_cred(cred);
+
+ return error;
+}
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
new file mode 100644
index 0000000..3153784
--- /dev/null
+++ b/security/apparmor/resource.c
@@ -0,0 +1,103 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor resource mediation and attachment
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/audit.h>
+
+#include "include/audit.h"
+#include "include/resource.h"
+#include "include/policy.h"
+
+struct aa_audit_resource {
+ struct aa_audit base;
+
+ int rlimit;
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_resource *sa = va;
+
+ if (sa->rlimit)
+ audit_log_format(ab, " rlimit=%d", sa->rlimit - 1);
+}
+
+static int aa_audit_resource(struct aa_profile *profile,
+ struct aa_audit_resource *sa)
+{
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
+ audit_cb);
+}
+
+/**
+ * aa_task_setrlimit - test permission to set an rlimit
+ * @profile - profile confining the task
+ * @resource - the resource being set
+ * @new_rlim - the new resource limit
+ *
+ * Control raising the processes hard limit.
+ */
+int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ struct aa_audit_resource sa = {
+ .base.operation = "setrlimit",
+ .base.gfp_mask = GFP_KERNEL,
+ .rlimit = resource + 1,
+ };
+ int error = 0;
+
+ if (profile->rlimits.mask & (1 << resource) &&
+ new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max) {
+ sa.base.error = -EACCES;
+
+ error = aa_audit_resource(profile, &sa);
+ }
+
+ return error;
+}
+
+void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
+{
+ unsigned int mask = 0;
+ struct rlimit *rlim, *initrlim;
+ int i;
+
+ /* for any rlimits the profile controlled reset the soft limit
+ * to the less of the tasks hard limit and the init tasks soft limit
+ */
+ if (old && old->rlimits.mask) {
+ for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
+ if (old->rlimits.mask & mask) {
+ rlim = current->signal->rlim + i;
+ initrlim = init_task.signal->rlim + i;
+ rlim->rlim_cur = min(rlim->rlim_max,
+ initrlim->rlim_cur);
+ }
+ }
+ }
+
+ /* set any new hard limits as dictated by the new profile */
+ if (!(new && new->rlimits.mask))
+ return;
+ for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
+ if (!(new->rlimits.mask & mask))
+ continue;
+
+ rlim = current->signal->rlim + i;
+ rlim->rlim_max = min(rlim->rlim_max,
+ new->rlimits.limits[i].rlim_max);
+ /* soft limit should not exceed hard limit */
+ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
+ }
+}
--
1.6.3.3

2009-11-10 16:13:48

by John Johansen

[permalink] [raw]
Subject: [PATCH 10/12] AppArmor: domain functions for domain transition

AppArmor routines for controling domain transitions, which can occur at
exec or through self directed change_profile/change_hat calls.

Unconfined tasks are checked at exec against the profiles in the confining
profile namespace to determine if a profile should be attached to the task.

Confined tasks execs are controlled by the profile which provides rules
determining which execs are allowed and if so which profiles should be
transitioned to.

Self directed domain transitions allow a task to request transition
to a given profile. If the transition is allowed then the profile will
be applied, either immeditately or at exec time depending on the request.
Immeditate self directed transitions have several security limitations
but have uses in setting up stub transition profiles and other limited
cases.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/domain.c | 691 ++++++++++++++++++++++++++++++++++++
security/apparmor/include/domain.h | 36 ++
2 files changed, 727 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/domain.c
create mode 100644 security/apparmor/include/domain.h

diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
new file mode 100644
index 0000000..d58231a
--- /dev/null
+++ b/security/apparmor/domain.c
@@ -0,0 +1,691 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy attachment and domain transitions
+ *
+ * Copyright (C) 2002-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/errno.h>
+#include <linux/fdtable.h>
+#include <linux/file.h>
+#include <linux/mount.h>
+#include <linux/syscalls.h>
+#include <linux/tracehook.h>
+#include <linux/personality.h>
+
+#include "include/audit.h"
+#include "include/apparmorfs.h"
+#include "include/context.h"
+#include "include/domain.h"
+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/match.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+/**
+ * aa_free_domain_entries - free entries in a domain table
+ * @domain: the domain table to free
+ */
+void aa_free_domain_entries(struct aa_domain *domain)
+{
+ int i;
+
+ if (!domain->table)
+ return;
+
+ for (i = 0; i < domain->size; i++)
+ kfree(domain->table[i]);
+ kfree(domain->table);
+}
+
+/*
+ * check if the task is ptraced and if so if the tracing task is allowed
+ * to trace the new domain
+ */
+static int aa_may_change_ptraced_domain(struct task_struct *task,
+ struct aa_profile *to_profile)
+{
+ struct task_struct *tracer;
+ struct cred *cred = NULL;
+ struct aa_profile *tracerp = NULL;
+ int error = 0;
+
+ rcu_read_lock();
+ tracer = tracehook_tracer_task(task);
+ if (tracer)
+ /* released below */
+ cred = aa_get_task_policy(tracer, &tracerp);
+ rcu_read_unlock();
+
+ /* not ptraced */
+ if (!tracer)
+ return 0;
+
+ if (!tracerp)
+ goto out;
+
+ error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
+
+out:
+ put_cred(cred);
+
+ return error;
+}
+
+/**
+ * change_profile_perms - find permissions for change_profile
+ * @profile: the current profile
+ * @ns: the namespace being switched to
+ * @name: the name of the profile to change to
+ * @rstate: if !NULL will contain the state the match finished in
+ */
+static struct file_perms change_profile_perms(struct aa_profile *profile,
+ struct aa_namespace *ns,
+ const char *name,
+ unsigned int *rstate)
+{
+ struct file_perms perms;
+ struct path_cond cond = { };
+ unsigned int state;
+
+ if (!profile) {
+ /* unconfined */
+ perms.allowed = AA_MAY_CHANGE_PROFILE;
+ perms.xindex = perms.dindex = 0;
+ perms.audit = perms.quiet = perms.kill = 0;
+ if (rstate)
+ *rstate = 0;
+ return perms;
+ } else if (!profile->file.dfa) {
+ return nullperms;
+ } else if ((ns == profile->ns)) {
+ /* try matching against rules with out namespace prependend */
+ perms = aa_str_perms(profile->file.dfa, DFA_START, name, &cond,
+ rstate);
+ if (COMBINED_PERM_MASK(perms) & AA_MAY_CHANGE_PROFILE)
+ return perms;
+ }
+
+ /* try matching with namespace name and then profile */
+ state = aa_dfa_match(profile->file.dfa, DFA_START, ns->base.name);
+ state = aa_dfa_null_transition(profile->file.dfa, state);
+ return aa_str_perms(profile->file.dfa, state, name, &cond, rstate);
+}
+
+static const char *next_name(int xtype, const char *name)
+{
+ return NULL;
+}
+
+/* __aa_attach_match_ - find an attachment match
+ * @name - to match against
+ * @head - profile list to walk
+ *
+ * Do a linear search on the profiles in the list. There is a matching
+ * preference where an exact match is prefered over a name which uses
+ * expressions to match, and matching expressions with the greatest
+ * xmatch_len are prefered.
+ */
+static struct aa_profile *__aa_attach_match(const char *name,
+ struct list_head *head)
+{
+ int len = 0;
+ struct aa_profile *profile, *candidate = NULL;
+
+ list_for_each_entry(profile, head, base.list) {
+ if (profile->flags & PFLAG_NULL)
+ continue;
+ if (profile->xmatch && profile->xmatch_len > len) {
+ unsigned int state = aa_dfa_match(profile->xmatch,
+ DFA_START, name);
+ u16 perm = dfa_user_allow(profile->xmatch, state);
+ /* any accepting state means a valid match. */
+ if (perm & MAY_EXEC) {
+ candidate = profile;
+ len = profile->xmatch_len;
+ }
+ } else if (!strcmp(profile->base.name, name))
+ /* exact non-re match, no more searching required */
+ return profile;
+ }
+
+ return candidate;
+}
+
+/**
+ * aa_sys_find_attach - do attachment search for sys unconfined processes
+ * @base: the base to search
+ * name: the executable name to match against
+ */
+static struct aa_profile *aa_sys_find_attach(struct aa_policy_common *base,
+ const char *name)
+{
+ struct aa_profile *profile;
+
+ read_lock(&base->lock);
+ profile = aa_get_profile(__aa_attach_match(name, &base->profiles));
+ read_unlock(&base->lock);
+
+ return profile;
+}
+
+/**
+ * x_to_profile - get target profile for a given xindex
+ * @ns: namespace of profile
+ * @profile: current profile
+ * @name: to to lookup if specified
+ * @xindex: index into x transition table
+ *
+ * find profile for a transition index
+ *
+ * Returns: refcounted profile or ERR_PTR
+ */
+static struct aa_profile *x_to_profile(struct aa_namespace *ns,
+ struct aa_profile *profile,
+ const char *name, u16 xindex)
+{
+ struct aa_profile *new_profile = NULL;
+ u16 xtype = xindex & AA_X_TYPE_MASK;
+ int index = xindex & AA_X_INDEX_MASK;
+
+ if (!profile)
+ profile = ns->unconfined;
+
+ switch (xtype) {
+ case AA_X_NONE:
+ /* fail exec unless ix || ux fallback - handled by caller */
+ return ERR_PTR(-EACCES);
+ case AA_X_NAME:
+ if (xindex & AA_X_CHILD)
+ /* released by caller */
+ new_profile = aa_sys_find_attach(&profile->base, name);
+ else
+ /* released by caller */
+ new_profile = aa_sys_find_attach(&ns->base, name);
+
+ goto out;
+ case AA_X_TABLE:
+ if (index > profile->file.trans.size) {
+ AA_ERROR("Invalid named transition\n");
+ return ERR_PTR(-EACCES);
+ }
+ name = profile->file.trans.table[index];
+ break;
+ }
+
+ for (; !new_profile && name; name = next_name(xtype, name)) {
+ struct aa_namespace *new_ns;
+ const char *xname = NULL;
+
+ new_ns = NULL;
+ if (xindex & AA_X_CHILD) {
+ /* release by caller */
+ new_profile = aa_find_child(profile, name);
+ if (new_profile)
+ return new_profile;
+ continue;
+ } else if (*name == ':') {
+ /* switching namespace */
+ const char *ns_name = name + 1;
+ name = xname = ns_name + strlen(ns_name) + 1;
+ if (!*xname)
+ /* no name so use profile name */
+ xname = profile->fqname;
+ if (*ns_name == '@') {
+ /* TODO: variable support */
+ ;
+ }
+ /* released below */
+ new_ns = aa_find_namespace(ns_name);
+ if (!new_ns)
+ continue;
+ } else if (*name == '@') {
+ /* TODO: variable support */
+
+ } else {
+ xname = name;
+ }
+
+ /* released by caller */
+ new_profile = aa_find_profile(new_ns ? new_ns : ns, xname);
+ aa_put_namespace(new_ns);
+ }
+
+out:
+ if (!new_profile)
+ return ERR_PTR(-ENOENT);
+
+ /* released by caller */
+ return new_profile;
+}
+
+/**
+ * apparmor_bprm_set_creds - set the new creds on the bprm struct
+ * @bprm: binprm for the exec
+ */
+int apparmor_bprm_set_creds(struct linux_binprm *bprm)
+{
+ struct aa_task_context *cxt;
+ struct aa_profile *profile, *new_profile = NULL;
+ struct aa_namespace *ns;
+ char *buffer = NULL;
+ unsigned int state = DFA_START;
+ struct path_cond cond = {
+ bprm->file->f_path.dentry->d_inode->i_uid,
+ bprm->file->f_path.dentry->d_inode->i_mode
+ };
+ struct aa_audit_file sa = {
+ .base.operation = "exec",
+ .base.gfp_mask = GFP_KERNEL,
+ .request = MAY_EXEC,
+ .cond = &cond,
+ };
+
+ sa.base.error = cap_bprm_set_creds(bprm);
+ if (sa.base.error)
+ return sa.base.error;
+
+ if (bprm->cred_prepared)
+ return 0;
+
+ cxt = bprm->cred->security;
+ BUG_ON(!cxt);
+
+ profile = aa_confining_profile(cxt->sys.profile);
+ ns = profile->ns;
+
+ /* buffer freed below, name is pointer inside of buffer */
+ sa.base.error = aa_get_name(&bprm->file->f_path, 0, &buffer,
+ (char **)&sa.name);
+ if (sa.base.error) {
+ if (!profile || profile->flags & PFLAG_IX_ON_NAME_ERROR)
+ sa.base.error = 0;
+ sa.base.info = "Exec failed name resolution";
+ sa.name = bprm->filename;
+ goto audit;
+ }
+
+ if (!profile) {
+ /* unconfined task - attach profile if one matches */
+ new_profile = aa_sys_find_attach(&ns->base, sa.name);
+ if (!new_profile)
+ goto cleanup;
+ goto apply;
+ } else if (cxt->sys.onexec) {
+ /*
+ * onexec permissions are stored in a pair, rewalk the
+ * dfa to get start of the exec path match.
+ */
+ sa.perms = change_profile_perms(profile, cxt->sys.onexec->ns,
+ sa.name, &state);
+ state = aa_dfa_null_transition(profile->file.dfa, state);
+ }
+ sa.perms = aa_str_perms(profile->file.dfa, state, sa.name, &cond, NULL);
+ if (cxt->sys.onexec && sa.perms.allowed & AA_MAY_ONEXEC) {
+ new_profile = cxt->sys.onexec;
+ cxt->sys.onexec = NULL;
+ sa.base.info = "change_profile onexec";
+ } else if (sa.perms.allowed & MAY_EXEC) {
+ new_profile = x_to_profile(ns, profile, sa.name,
+ sa.perms.xindex);
+ if (IS_ERR(new_profile)) {
+ if (sa.perms.xindex & AA_X_INHERIT) {
+ /* (p|c|n)ix - don't change profile */
+ sa.base.info = "ix fallback";
+ goto x_clear;
+ } else if (sa.perms.xindex & AA_X_UNCONFINED) {
+ new_profile = aa_get_profile(ns->unconfined);
+ sa.base.info = "ux fallback";
+ } else {
+ sa.base.error = PTR_ERR(new_profile);
+ if (sa.base.error == -ENOENT)
+ sa.base.info = "profile not found";
+ new_profile = NULL;
+ }
+ }
+ } else if (PROFILE_COMPLAIN(profile)) {
+ new_profile = aa_alloc_null_profile(profile, 0);
+ sa.base.error = -EACCES;
+ if (!new_profile)
+ sa.base.error = -ENOMEM;
+ sa.name2 = new_profile->fqname;
+ sa.perms.xindex |= AA_X_UNSAFE;
+ } else {
+ sa.base.error = -EACCES;
+ }
+
+ if (!new_profile)
+ goto audit;
+
+ if (profile == new_profile) {
+ aa_put_profile(new_profile);
+ goto audit;
+ }
+
+ if (bprm->unsafe & LSM_UNSAFE_SHARE) {
+ /* FIXME: currently don't mediate shared state */
+ ;
+ }
+
+ if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
+ sa.base.error = aa_may_change_ptraced_domain(current,
+ new_profile);
+ if (sa.base.error)
+ goto audit;
+ }
+
+ /* Determine if secure exec is needed.
+ * Can be at this point for the following reasons:
+ * 1. unconfined switching to confined
+ * 2. confined switching to different confinement
+ * 3. confined switching to unconfined
+ *
+ * Cases 2 and 3 are marked as requiring secure exec
+ * (unless policy specified "unsafe exec")
+ *
+ * bprm->unsafe is used to cache the AA_X_UNSAFE permission
+ * to avoid having to recompute in secureexec
+ */
+ if (!(sa.perms.xindex & AA_X_UNSAFE))
+ bprm->unsafe |= AA_SECURE_X_NEEDED;
+
+apply:
+ sa.name2 = new_profile->fqname;
+ /* When switching namespace ensure its part of audit message */
+ if (new_profile->ns != ns)
+ sa.name3 = new_profile->ns->base.name;
+
+ /* when transitioning profiles clear unsafe personality bits */
+ bprm->per_clear |= PER_CLEAR_ON_SETID;
+
+ aa_put_profile(cxt->sys.profile);
+ /* transfer new profile reference will be released when cxt is freed */
+ cxt->sys.profile = new_profile;
+
+x_clear:
+ aa_put_profile(cxt->sys.previous);
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.previous = NULL;
+ cxt->sys.onexec = NULL;
+ cxt->sys.token = 0;
+
+audit:
+ sa.base.error = aa_audit_file(profile, &sa);
+
+cleanup:
+ kfree(buffer);
+
+ return sa.base.error;
+}
+
+int apparmor_bprm_secureexec(struct linux_binprm *bprm)
+{
+ int ret = cap_bprm_secureexec(bprm);
+
+ /* the decision to use secure exec is computed in set_creds
+ * and stored in bprm->unsafe.
+ */
+ if (!ret && (bprm->unsafe & AA_SECURE_X_NEEDED))
+ ret = 1;
+
+ return ret;
+}
+
+void apparmor_bprm_committing_creds(struct linux_binprm *bprm)
+{
+ struct aa_profile *profile;
+ /* ref released below */
+ struct cred *cred = aa_get_task_policy(current, &profile);
+ struct aa_task_context *new_cxt = bprm->cred->security;
+
+ /* bail out if unconfiged or not changing profile */
+ if ((new_cxt->sys.profile == profile) ||
+ (new_cxt->sys.profile->flags & PFLAG_UNCONFINED)) {
+ put_cred(cred);
+ return;
+ }
+ put_cred(cred);
+
+ current->pdeath_signal = 0;
+
+ /* reset soft limits and set hard limits for the new profile */
+ __aa_transition_rlimits(profile, new_cxt->sys.profile);
+}
+
+void apparmor_bprm_committed_creds(struct linux_binprm *bprm)
+{
+ /* TODO: cleanup signals - ipc mediation */
+ return;
+}
+
+/*
+ * Functions for self directed profile change
+ */
+
+static char *new_compound_name(const char *n1, const char *n2)
+{
+ char *name = kmalloc(strlen(n1) + strlen(n2) + 3, GFP_KERNEL);
+ if (name)
+ sprintf(name, "%s//%s", n1, n2);
+ return name;
+}
+
+/**
+ * aa_change_hat - change hat to/from subprofile
+ * @hat_name: hat to change to
+ * @token: magic value to validate the hat change
+ * @permtest: true if this is just a permission test
+ *
+ * Change to new @hat_name, and store the @hat_magic in the current task
+ * context. If the new @hat_name is %NULL and the @token matches that
+ * stored in the current task context and is not 0, return to the top level
+ * profile.
+ * Returns %0 on success, error otherwise.
+ */
+int aa_change_hat(const char *hat_name, u64 token, int permtest)
+{
+ const struct cred *cred;
+ struct aa_task_context *cxt;
+ struct aa_profile *profile, *previous_profile, *hat = NULL;
+ struct aa_audit_file sa = {
+ .base.gfp_mask = GFP_KERNEL,
+ .base.operation = "change_hat",
+ .request = AA_MAY_CHANGEHAT,
+ };
+ char *name = NULL;
+
+ cred = aa_current_policy(&profile);
+ cxt = cred->security;
+ previous_profile = cxt->sys.previous;
+
+ if (!profile) {
+ sa.base.info = "unconfined";
+ sa.base.error = -EPERM;
+ goto audit;
+ }
+
+ if (hat_name) {
+ struct aa_profile *root;
+ root = PROFILE_IS_HAT(profile) ? profile->parent : profile;
+ sa.name2 = profile->ns->base.name;
+
+ /* released below */
+ hat = aa_find_child(root, hat_name);
+ if (!hat) {
+ if (permtest || !PROFILE_COMPLAIN(root))
+ /* probing is an expected unfortunate behavior
+ * of the change_hat api is traditionally quiet
+ */
+ goto out;
+
+ /* freed below */
+ name = new_compound_name(root->fqname, hat_name);
+
+ sa.name = name;
+ sa.base.info = "hat not found";
+ sa.base.error = -ENOENT;
+ /* released below */
+ hat = aa_alloc_null_profile(profile, 1);
+ if (!hat) {
+ sa.base.info = "failed null profile create";
+ sa.base.error = -ENOMEM;
+ goto audit;
+ }
+ } else {
+ sa.name = hat->fqname;
+ if (!PROFILE_IS_HAT(hat)) {
+ sa.base.info = "target not hat";
+ sa.base.error = -EPERM;
+ goto audit;
+ }
+ }
+
+ sa.base.error = aa_may_change_ptraced_domain(current, hat);
+ if (sa.base.error) {
+ sa.base.info = "ptraced";
+ sa.base.error = -EPERM;
+ goto audit;
+ }
+
+ if (!permtest) {
+ sa.base.error = aa_set_current_hat(hat, token);
+ if (sa.base.error == -EACCES)
+ sa.perms.kill = AA_MAY_CHANGEHAT;
+ else if (name && !sa.base.error)
+ /* reset error for learning of new hats */
+ sa.base.error = -ENOENT;
+ }
+ } else if (previous_profile) {
+ sa.name = previous_profile->fqname;
+ sa.base.error = aa_restore_previous_profile(token);
+ sa.perms.kill = AA_MAY_CHANGEHAT;
+ } else
+ /* ignore restores when there is no saved profile */
+ goto out;
+
+audit:
+ if (!permtest)
+ sa.base.error = aa_audit_file(profile, &sa);
+
+out:
+ aa_put_profile(hat);
+ kfree(name);
+
+ return sa.base.error;
+}
+
+/**
+ * aa_change_profile - perform a one-way profile transition
+ * @ns_name: name of the profile namespace to change to
+ * @fqname: name of profile to change to
+ * @onexec: whether this transition is to take place immediately or at exec
+ * @permtest: true if this is just a permission test
+ *
+ * Change to new profile @name. Unlike with hats, there is no way
+ * to change back. If @onexec then the transition is delayed until
+ * the next exec.
+ *
+ * Returns %0 on success, error otherwise.
+ */
+int aa_change_profile(const char *ns_name, const char *fqname, int onexec,
+ int permtest)
+{
+ const struct cred *cred;
+ struct aa_task_context *cxt;
+ struct aa_profile *profile, *target = NULL;
+ struct aa_namespace *ns = NULL;
+ struct aa_audit_file sa = {
+ .request = AA_MAY_CHANGE_PROFILE,
+ .base.gfp_mask = GFP_KERNEL,
+ };
+
+ if (!fqname && !ns_name)
+ return -EINVAL;
+
+ if (onexec)
+ sa.base.operation = "change_onexec";
+ else
+ sa.base.operation = "change_profile";
+
+ cred = aa_current_policy(&profile);
+ cxt = cred->security;
+
+ if (ns_name) {
+ sa.name2 = ns_name;
+ /* released below */
+ ns = aa_find_namespace(ns_name);
+ if (!ns) {
+ /* we don't create new namespace in complain mode */
+ sa.base.info = "namespace not found";
+ sa.base.error = -ENOENT;
+ goto audit;
+ }
+ } else {
+ /* released below */
+ ns = aa_get_namespace(cxt->sys.profile->ns);
+ sa.name2 = ns->base.name;
+ }
+
+ /* if the name was not specified, use the name of the current profile */
+ if (!fqname) {
+ if (!profile)
+ fqname = ns->unconfined->fqname;
+ else
+ fqname = profile->fqname;
+ }
+ sa.name = fqname;
+
+ sa.perms = change_profile_perms(profile, ns, fqname, NULL);
+ if (!(sa.perms.allowed & AA_MAY_CHANGE_PROFILE)) {
+ sa.base.error = -EACCES;
+ goto audit;
+ }
+
+ /* released below */
+ target = aa_find_profile(ns, fqname);
+ if (!target) {
+ sa.base.info = "profile not found";
+ sa.base.error = -ENOENT;
+ if (permtest || !PROFILE_COMPLAIN(profile))
+ goto audit;
+ /* release below */
+ target = aa_alloc_null_profile(profile, 0);
+ if (!target) {
+ sa.base.info = "failed null profile create";
+ sa.base.error = -ENOMEM;
+ goto audit;
+ }
+ }
+
+ /* check if tracing task is allowed to trace target domain */
+ sa.base.error = aa_may_change_ptraced_domain(current, target);
+ if (sa.base.error) {
+ sa.base.info = "ptrace prevents transition";
+ goto audit;
+ }
+
+ if (permtest)
+ goto audit;
+
+ if (onexec)
+ sa.base.error = aa_set_current_onexec(target);
+ else
+ sa.base.error = aa_replace_current_profiles(target);
+
+audit:
+ if (!permtest)
+ sa.base.error = aa_audit_file(profile, &sa);
+
+ aa_put_namespace(ns);
+ aa_put_profile(target);
+
+ return sa.base.error;
+}
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h
new file mode 100644
index 0000000..c7fd38f
--- /dev/null
+++ b/security/apparmor/include/domain.h
@@ -0,0 +1,36 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor security domain transition function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/binfmts.h>
+#include <linux/types.h>
+
+#ifndef __AA_DOMAIN_H
+#define __AA_DOMAIN_H
+
+struct aa_domain {
+ int size;
+ char **table;
+};
+
+int apparmor_bprm_set_creds(struct linux_binprm *bprm);
+int apparmor_bprm_secureexec(struct linux_binprm *bprm);
+void apparmor_bprm_committing_creds(struct linux_binprm *bprm);
+void apparmor_bprm_committed_creds(struct linux_binprm *bprm);
+
+void aa_free_domain_entries(struct aa_domain *domain);
+int aa_change_hat(const char *hat_name, u64 token, int permtest);
+int aa_change_profile(const char *ns_name, const char *name, int onexec,
+ int permtest);
+
+#endif /* __AA_DOMAIN_H */
--
1.6.3.3

2009-11-10 16:13:56

by John Johansen

[permalink] [raw]
Subject: [PATCH 11/12] AppArmor: LSM interface, and security module initialization

AppArmor hooks to interface with the LSM, and module parameters and
initialization.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/lsm.c | 1063 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1063 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/lsm.c

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
new file mode 100644
index 0000000..6ee3480
--- /dev/null
+++ b/security/apparmor/lsm.c
@@ -0,0 +1,1063 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor LSM hooks.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * 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.
+ */
+
+#include <linux/security.h>
+#include <linux/moduleparam.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/ptrace.h>
+#include <linux/ctype.h>
+#include <linux/sysctl.h>
+#include <linux/audit.h>
+#include <net/sock.h>
+
+#include "include/apparmor.h"
+#include "include/apparmorfs.h"
+#include "include/audit.h"
+#include "include/capability.h"
+#include "include/context.h"
+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/net.h"
+#include "include/path.h"
+#include "include/policy.h"
+#include "include/procattr.h"
+
+/* Flag indicating whether initialization completed */
+int apparmor_initialized;
+
+/*
+ * LSM hook functions
+ */
+
+/*
+ * free the associated aa_task_context and put its profiles
+ */
+static void apparmor_cred_free(struct cred *cred)
+{
+ aa_free_task_context(cred->security);
+ cred->security = NULL;
+}
+
+/*
+ * allocate the apparmor part of blank credentials
+ */
+static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+ /* freed by apparmor_cred_free */
+ struct aa_task_context *cxt = aa_alloc_task_context(gfp);
+ if (cxt)
+ return -ENOMEM;
+
+ cred->security = cxt;
+ return 0;
+}
+
+/*
+ * prepare new aa_task_context for modification by prepare_cred block
+ */
+static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ /* freed by apparmor_cred_free */
+ struct aa_task_context *cxt = aa_alloc_task_context(gfp);
+ if (!cxt)
+ return -ENOMEM;
+
+ aa_dup_task_context(cxt, old->security);
+ new->security = cxt;
+ return 0;
+}
+
+/*
+ * transfer the apparmor data to a blank set of creds
+ */
+static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
+{
+ const struct aa_task_context *old_cxt = old->security;
+ struct aa_task_context *new_cxt = new->security;
+
+ aa_dup_task_context(new_cxt, old_cxt);
+}
+
+static int apparmor_ptrace_access_check(struct task_struct *child,
+ unsigned int mode)
+{
+ return aa_ptrace(current, child, mode);
+}
+
+static int apparmor_ptrace_traceme(struct task_struct *parent)
+{
+ return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+}
+
+/* Derived from security/commoncap.c:cap_capget */
+static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
+ kernel_cap_t *inheritable, kernel_cap_t *permitted)
+{
+ struct aa_profile *profile;
+ const struct cred *cred;
+
+ rcu_read_lock();
+ cred = __task_cred(target);
+ aa_cred_policy(cred, &profile);
+
+ *effective = cred->cap_effective;
+ *inheritable = cred->cap_inheritable;
+ *permitted = cred->cap_permitted;
+
+ if (profile) {
+ *effective = cap_combine(*effective, profile->caps.set);
+ *effective = cap_intersect(*effective, profile->caps.allowed);
+ }
+ rcu_read_unlock();
+
+ return 0;
+}
+
+static int apparmor_capable(struct task_struct *task, const struct cred *cred,
+ int cap, int audit)
+{
+ struct aa_profile *profile;
+ /* cap_capable returns 0 on success, else -EPERM */
+ int error = cap_capable(task, cred, cap, audit);
+
+ aa_cred_policy(cred, &profile);
+ if (profile && (!error || cap_raised(profile->caps.set, cap)))
+ error = aa_capable(task, profile, cap, audit);
+
+ return error;
+}
+
+static int apparmor_sysctl(struct ctl_table *table, int op)
+{
+ int error = 0;
+ struct aa_profile *profile = aa_current_profile_wupd();
+
+ if (profile) {
+ char *buffer, *name;
+ int mask;
+
+ mask = 0;
+ if (op & 4)
+ mask |= MAY_READ;
+ if (op & 2)
+ mask |= MAY_WRITE;
+
+ error = -ENOMEM;
+ /* freed below */
+ buffer = (char *)__get_free_page(GFP_KERNEL);
+ if (!buffer)
+ goto out;
+
+ /*
+ * TODO: convert this over to using a global or per
+ * namespace control instead of a hard coded /proc
+ */
+ name = sysctl_pathname(table, buffer, PAGE_SIZE);
+ if (name && name - buffer >= 5) {
+ struct path_cond cond = { 0, S_IFREG };
+ name -= 5;
+ memcpy(name, "/proc", 5);
+ error = aa_pathstr_perm(profile, "sysctl", name, mask,
+ &cond);
+ }
+ free_page((unsigned long)buffer);
+ }
+
+out:
+ return error;
+}
+
+static int common_perm(const char *op, struct path *path, u16 mask,
+ struct path_cond *cond)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ profile = aa_current_profile();
+ if (profile)
+ error = aa_path_perm(profile, op, path, mask, cond);
+
+ return error;
+}
+
+static int common_perm_dentry(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask,
+ struct path_cond *cond)
+{
+ struct path path = { dir->mnt, dentry };
+
+ return common_perm(op, &path, mask, cond);
+}
+
+static int common_perm_rm(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask)
+{
+ struct inode *inode = dentry->d_inode;
+ struct path_cond cond = { };
+
+ if (!dir->mnt || !inode || !mediated_filesystem(inode))
+ return 0;
+
+ cond.uid = inode->i_uid;
+ cond.mode = inode->i_mode;
+
+ return common_perm_dentry(op, dir, dentry, mask, &cond);
+}
+
+static int common_perm_create(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask, umode_t mode)
+{
+ struct path_cond cond = { current_fsuid(), mode };
+
+ if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))
+ return 0;
+
+ return common_perm_dentry(op, dir, dentry, mask, &cond);
+}
+
+static int apparmor_path_unlink(struct path *dir, struct dentry *dentry)
+{
+ return common_perm_rm("unlink", dir, dentry, MAY_WRITE);
+}
+
+static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry,
+ int mode)
+{
+ return common_perm_create("mkdir", dir, dentry, AA_MAY_CREATE, S_IFDIR);
+}
+
+static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry)
+{
+ return common_perm_rm("rmdir", dir, dentry, MAY_WRITE);
+}
+
+static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
+ int mode, unsigned int dev)
+{
+ return common_perm_create("mknod", dir, dentry, AA_MAY_CREATE, mode);
+}
+
+static int apparmor_path_truncate(struct path *path, loff_t length,
+ unsigned int time_attrs)
+{
+ struct path_cond cond = { path->dentry->d_inode->i_uid,
+ path->dentry->d_inode->i_mode
+ };
+
+ if (!path->mnt || !mediated_filesystem(path->dentry->d_inode))
+ return 0;
+ return common_perm("truncate", path, MAY_WRITE, &cond);
+}
+
+static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
+ const char *old_name)
+{
+ return common_perm_create("symlink_create", dir, dentry, AA_MAY_CREATE,
+ S_IFLNK);
+}
+
+static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile)
+ error = aa_path_link(profile, old_dentry, new_dir, new_dentry);
+ return error;
+}
+
+static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile) {
+ struct path old_path = { old_dir->mnt, old_dentry };
+ struct path new_path = { new_dir->mnt, new_dentry };
+ struct path_cond cond = { old_dentry->d_inode->i_uid,
+ old_dentry->d_inode->i_mode
+ };
+
+ error = aa_path_perm(profile, "rename_src", &old_path,
+ MAY_READ | MAY_WRITE, &cond);
+ if (!error)
+ error = aa_path_perm(profile, "rename_dest", &new_path,
+ AA_MAY_CREATE | MAY_WRITE, &cond);
+
+ }
+ return error;
+}
+
+static int apparmor_dentry_open(struct file *file, const struct cred *cred)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ /* If in exec permission is handled by bprm hooks */
+ if (current->in_execve ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ aa_cred_policy(cred, &profile);
+ if (profile) {
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct path_cond cond = { inode->i_uid, inode->i_mode };
+
+ error = aa_path_perm(profile, "open", &file->f_path,
+ aa_map_file_to_perms(file), &cond);
+ /* released by aa_free_file_context */
+ fcxt->profile = aa_get_profile(profile);
+ /* todo cache actual allowed permissions */
+ fcxt->allowed = 0;
+ }
+
+ return error;
+}
+
+static int apparmor_file_alloc_security(struct file *file)
+{
+ /* freed by apparmor_file_free_security */
+ file->f_security = aa_alloc_file_context(GFP_KERNEL);
+ if (!file->f_security)
+ return -ENOMEM;
+ return 0;
+
+}
+
+static void apparmor_file_free_security(struct file *file)
+{
+ struct aa_file_cxt *cxt = file->f_security;
+
+ aa_free_file_context(cxt);
+}
+
+static int apparmor_file_permission(struct file *file, int mask)
+{
+ /*
+ * TODO: cache profiles that have revalidated?
+ */
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = fcxt->profile;
+ int error = 0;
+
+ if (!fprofile || !file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile();
+
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ /*
+ * AppArmor <= 2.4 revalidates files at access time instead
+ * of at exec.
+ */
+ if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
+ error = aa_file_perm(profile, "file_perm", file, mask);
+#endif
+
+ return error;
+}
+
+static int common_file_perm(const char *op, struct file *file, u16 mask)
+{
+ const struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = fcxt->profile;
+ int error = 0;
+
+ if (!fprofile || !file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
+ error = aa_file_perm(profile, op, file, mask);
+
+ return error;
+}
+
+static int apparmor_file_lock(struct file *file, unsigned int cmd)
+{
+ u16 mask = AA_MAY_LOCK;
+
+ if (cmd == F_WRLCK)
+ mask |= MAY_WRITE;
+
+ return common_file_perm("file_lock", file, mask);
+}
+
+static int common_mmap(struct file *file, const char *operation,
+ 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_path.dentry;
+ return common_file_perm(operation, file, mask);
+}
+
+static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags,
+ unsigned long addr, unsigned long addr_only)
+{
+ int rc = 0;
+ struct aa_profile *profile = aa_current_profile_wupd();
+ /*
+ * test before cap_file_mmap. For confined tasks AppArmor will
+ * enforce the mmap value set in the profile or default
+ * to LSM_MMAP_MIN_ADDR
+ */
+ if (profile) {
+ if (profile->flags & PFLAG_MMAP_MIN_ADDR) {
+ if (addr < profile->mmap_min_addr)
+ rc = -EACCES;
+ } else if (addr < CONFIG_LSM_MMAP_MIN_ADDR) {
+ rc = -EACCES;
+ }
+ if (rc) {
+ struct aa_audit sa = {
+ .operation = "file_mmap",
+ .gfp_mask = GFP_KERNEL,
+ .info = "addr < mmap_min_addr",
+ .error = rc,
+ };
+ return aa_audit(AUDIT_APPARMOR_DENIED, profile, &sa,
+ NULL);
+ }
+ }
+ rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only);
+ if (rc || addr_only)
+ return rc;
+
+ return common_mmap(file, "file_mmap", prot, flags);
+}
+
+static int apparmor_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+{
+ return common_mmap(vma->vm_file, "file_mprotect", prot,
+ !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
+}
+
+static int apparmor_getprocattr(struct task_struct *task, char *name,
+ char **value)
+{
+ int error = -ENOENT;
+ struct aa_namespace *ns;
+ struct aa_profile *profile, *onexec, *prev;
+ /* released below */
+ const struct cred *cred = aa_get_task_policy(task, &profile);
+ struct aa_task_context *cxt = cred->security;
+ ns = cxt->sys.profile->ns;
+ onexec = cxt->sys.onexec;
+ prev = cxt->sys.previous;
+
+ /* task must be either querying itself, unconfined or can ptrace */
+ if (current != task && profile && !capable(CAP_SYS_PTRACE)) {
+ error = -EPERM;
+ } else {
+ if (strcmp(name, "current") == 0) {
+ error = aa_getprocattr(ns, profile, value);
+ } else if (strcmp(name, "prev") == 0) {
+ if (prev)
+ error = aa_getprocattr(ns, prev, value);
+ } else if (strcmp(name, "exec") == 0) {
+ if (onexec)
+ error = aa_getprocattr(ns, onexec, value);
+ } else {
+ error = -EINVAL;
+ }
+ }
+
+ put_cred(cred);
+
+ return error;
+}
+
+static int apparmor_setprocattr(struct task_struct *task, char *name,
+ void *value, size_t size)
+{
+ char *command, *args;
+ int error;
+
+ if (size == 0 || size >= PAGE_SIZE)
+ return -EINVAL;
+
+ /* task can only write its own attributes */
+ if (current != task)
+ return -EACCES;
+
+ 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(name, "current") == 0) {
+ if (strcmp(command, "changehat") == 0) {
+ error = aa_setprocattr_changehat(args, !AA_DO_TEST);
+ } else if (strcmp(command, "permhat") == 0) {
+ error = aa_setprocattr_changehat(args, AA_DO_TEST);
+ } else if (strcmp(command, "changeprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, 0,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, 0,
+ AA_DO_TEST);
+ } else if (strcmp(command, "permipc") == 0) {
+ error = aa_setprocattr_permipc(args);
+ } else {
+ struct aa_audit sa = {
+ .operation = "setprocattr",
+ .gfp_mask = GFP_KERNEL,
+ .info = name,
+ .error = -EINVAL,
+ };
+ return aa_audit(AUDIT_APPARMOR_DENIED, NULL, &sa, NULL);
+ }
+ } else if (strcmp(name, "exec") == 0) {
+ error = aa_setprocattr_changeprofile(strstrip(args), 1,
+ !AA_DO_TEST);
+ } else {
+ /* only support the "current" and "exec" process attributes */
+ return -EINVAL;
+ }
+ if (!error)
+ error = size;
+ return error;
+}
+
+static int apparmor_task_setrlimit(unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ struct aa_profile *profile = aa_current_profile_wupd();
+ int error = 0;
+
+ if (profile)
+ error = aa_task_setrlimit(profile, resource, new_rlim);
+
+ return error;
+}
+
+#ifdef CONFIG_SECURITY_APPARMOR_NETWORK
+static int apparmor_socket_create(int family, int type, int protocol, int kern)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (kern)
+ return 0;
+
+ profile = aa_current_profile();
+ if (profile)
+ error = aa_net_perm(profile, "socket_create", family,
+ type, protocol);
+ return error;
+}
+
+static int apparmor_socket_post_create(struct socket *sock, int family,
+ int type, int protocol, int kern)
+{
+ struct sock *sk = sock->sk;
+
+ if (kern)
+ return 0;
+
+ return aa_revalidate_sk(sk, "socket_post_create");
+}
+
+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_bind");
+}
+
+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_connect");
+}
+
+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_listen");
+}
+
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_accept");
+}
+
+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_sendmsg");
+}
+
+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_recvmsg");
+}
+
+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_getsockname");
+}
+
+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_getpeername");
+}
+
+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_getsockopt");
+}
+
+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_setsockopt");
+}
+
+static int apparmor_socket_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_shutdown");
+}
+#endif
+
+static struct security_operations apparmor_ops = {
+ .name = "apparmor",
+
+ .ptrace_access_check = apparmor_ptrace_access_check,
+ .ptrace_traceme = apparmor_ptrace_traceme,
+ .capget = apparmor_capget,
+ .sysctl = apparmor_sysctl,
+ .capable = apparmor_capable,
+
+ .path_link = apparmor_path_link,
+ .path_unlink = apparmor_path_unlink,
+ .path_symlink = apparmor_path_symlink,
+ .path_mkdir = apparmor_path_mkdir,
+ .path_rmdir = apparmor_path_rmdir,
+ .path_mknod = apparmor_path_mknod,
+ .path_rename = apparmor_path_rename,
+ .path_truncate = apparmor_path_truncate,
+ .dentry_open = apparmor_dentry_open,
+
+ .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,
+ .file_lock = apparmor_file_lock,
+
+ .getprocattr = apparmor_getprocattr,
+ .setprocattr = apparmor_setprocattr,
+
+#ifdef CONFIG_SECURITY_APPARMOR_NETWORK
+ .socket_create = apparmor_socket_create,
+ .socket_post_create = apparmor_socket_post_create,
+ .socket_bind = apparmor_socket_bind,
+ .socket_connect = apparmor_socket_connect,
+ .socket_listen = apparmor_socket_listen,
+ .socket_accept = apparmor_socket_accept,
+ .socket_sendmsg = apparmor_socket_sendmsg,
+ .socket_recvmsg = apparmor_socket_recvmsg,
+ .socket_getsockname = apparmor_socket_getsockname,
+ .socket_getpeername = apparmor_socket_getpeername,
+ .socket_getsockopt = apparmor_socket_getsockopt,
+ .socket_setsockopt = apparmor_socket_setsockopt,
+ .socket_shutdown = apparmor_socket_shutdown,
+#endif
+
+ .cred_alloc_blank = apparmor_cred_alloc_blank,
+ .cred_free = apparmor_cred_free,
+ .cred_prepare = apparmor_cred_prepare,
+ .cred_transfer = apparmor_cred_transfer,
+
+ .bprm_set_creds = apparmor_bprm_set_creds,
+ .bprm_committing_creds = apparmor_bprm_committing_creds,
+ .bprm_committed_creds = apparmor_bprm_committed_creds,
+ .bprm_secureexec = apparmor_bprm_secureexec,
+
+ .task_setrlimit = apparmor_task_setrlimit,
+};
+
+/*
+ * AppArmor sysfs module parameters
+ */
+
+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)
+
+static int param_set_aalockpolicy(const char *val, struct kernel_param *kp);
+static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp);
+#define param_check_aalockpolicy(name, p) __param_check(name, p, int)
+
+static int param_set_audit(const char *val, struct kernel_param *kp);
+static int param_get_audit(char *buffer, struct kernel_param *kp);
+#define param_check_audit(name, p) __param_check(name, p, int)
+
+static int param_set_mode(const char *val, struct kernel_param *kp);
+static int param_get_mode(char *buffer, struct kernel_param *kp);
+#define param_check_mode(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.
+ */
+
+/* AppArmor global enforcement switch - complain, enforce, kill */
+enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
+module_param_call(mode, param_set_mode, param_get_mode,
+ &aa_g_profile_mode, S_IRUSR | S_IWUSR);
+
+/* Debug mode */
+int aa_g_debug;
+module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
+
+/* Audit mode */
+enum audit_mode aa_g_audit;
+module_param_call(audit, param_set_audit, param_get_audit,
+ &aa_g_audit, S_IRUSR | S_IWUSR);
+
+/* Determines if audit header is included in audited messages. This
+ * provides more context if the audit daemon is not running
+ */
+int aa_g_audit_header;
+module_param_named(audit_header, aa_g_audit_header, aabool,
+ S_IRUSR | S_IWUSR);
+
+/* lock out loading/removal of policy
+ * TODO: add in at boot loading of policy, which is the only way to
+ * load policy, if lock_policy is set
+ */
+int aa_g_lock_policy;
+module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,
+ S_IRUSR | S_IWUSR);
+
+/* Syscall logging mode */
+int aa_g_logsyscall;
+module_param_named(logsyscall, aa_g_logsyscall, aabool,
+ S_IRUSR | S_IWUSR);
+
+/* Maximum pathname length before accesses will start getting rejected */
+unsigned int aa_g_path_max = 2 * PATH_MAX;
+module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR);
+
+/* Boot time disable flag */
+#ifdef CONFIG_SECURITY_APPARMOR_DISABLE
+#define AA_ENABLED_PERMS 0600
+#else
+#define AA_ENABLED_PERMS 0400
+#endif
+static int param_set_aa_enabled(const char *val, struct kernel_param *kp);
+static unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
+module_param_call(enabled, param_set_aa_enabled, param_get_aauint,
+ &apparmor_enabled, AA_ENABLED_PERMS);
+
+static int __init apparmor_enabled_setup(char *str)
+{
+ unsigned long enabled;
+ int error = strict_strtoul(str, 0, &enabled);
+ if (!error)
+ apparmor_enabled = enabled ? 1 : 0;
+ return 1;
+}
+
+__setup("apparmor=", apparmor_enabled_setup);
+
+/* set global flag turning off the ability to load policy */
+static int param_set_aalockpolicy(const char *val, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ if (aa_g_lock_policy)
+ return -EACCES;
+ return param_set_bool(val, kp);
+}
+
+static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_get_bool(buffer, kp);
+}
+
+static int param_set_aabool(const char *val, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_set_bool(val, kp);
+}
+
+static int param_get_aabool(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_get_bool(buffer, kp);
+}
+
+static int param_set_aauint(const char *val, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_set_uint(val, kp);
+}
+
+static int param_get_aauint(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_get_uint(buffer, kp);
+}
+
+/* allow run time disabling of apparmor */
+static int param_set_aa_enabled(const char *val, struct kernel_param *kp)
+{
+ unsigned long l;
+
+ if (!apparmor_initialized) {
+ apparmor_enabled = 0;
+ return 0;
+ }
+
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+
+ if (strict_strtoul(val, 0, &l) || l != 0)
+ return -EINVAL;
+
+ apparmor_enabled = 0;
+ apparmor_disable();
+ return 0;
+}
+
+static int param_get_audit(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
+}
+
+static int param_set_audit(const char *val, struct kernel_param *kp)
+{
+ int i;
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+
+ for (i = 0; i < AUDIT_MAX_INDEX; i++) {
+ if (strcmp(val, audit_mode_names[i]) == 0) {
+ aa_g_audit = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int param_get_mode(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]);
+}
+
+static int param_set_mode(const char *val, struct kernel_param *kp)
+{
+ int i;
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+
+ for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) {
+ if (strcmp(val, profile_mode_names[i]) == 0) {
+ aa_g_profile_mode = i;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * AppArmor init functions
+ */
+static int __init set_init_cxt(void)
+{
+ struct cred *cred = (struct cred *)current->real_cred;
+ struct aa_task_context *cxt;
+
+ cxt = aa_alloc_task_context(GFP_KERNEL);
+ if (!cxt)
+ return -ENOMEM;
+
+ cxt->sys.profile = aa_get_profile(default_namespace->unconfined);
+ cred->security = cxt;
+
+ return 0;
+}
+
+static int __init apparmor_init(void)
+{
+ int error;
+
+ if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) {
+ aa_info_message("AppArmor disabled by boot time parameter\n");
+ apparmor_enabled = 0;
+ return 0;
+ }
+
+ error = aa_alloc_default_namespace();
+ if (error) {
+ AA_ERROR("Unable to allocate default profile namespace\n");
+ goto alloc_out;
+ }
+
+ error = set_init_cxt();
+ if (error) {
+ AA_ERROR("Failed to set context on init task\n");
+ goto alloc_out;
+ }
+
+ error = register_security(&apparmor_ops);
+ if (error) {
+ AA_ERROR("Unable to register AppArmor\n");
+ goto register_security_out;
+ }
+
+ /* Report that AppArmor successfully initialized */
+ apparmor_initialized = 1;
+ if (aa_g_profile_mode == APPARMOR_COMPLAIN)
+ aa_info_message("AppArmor initialized: complain mode enabled");
+ else if (aa_g_profile_mode == APPARMOR_KILL)
+ aa_info_message("AppArmor initialized: kill mode enabled");
+ else
+ aa_info_message("AppArmor initialized");
+
+ return error;
+
+register_security_out:
+ aa_free_default_namespace();
+
+alloc_out:
+ aa_destroy_aafs();
+
+ apparmor_enabled = 0;
+ return error;
+
+}
+
+security_initcall(apparmor_init);
+
+void apparmor_disable(void)
+{
+ /* Remove and release all the profiles on the profile list. */
+ aa_profile_ns_list_release();
+
+ /* FIXME: cleanup profiles references on files */
+ aa_free_default_namespace();
+
+ aa_destroy_aafs();
+ apparmor_initialized = 0;
+
+ aa_info_message("AppArmor protection disabled");
+}
--
1.6.3.3

2009-11-10 16:14:06

by John Johansen

[permalink] [raw]
Subject: [PATCH 12/12] AppArmor: Enable configuring and building of the AppArmor security module

Kconfig and Makefiles to enable configuration and building of AppArmor.

Signed-off-by: John Johansen <[email protected]>
---
security/Kconfig | 1 +
security/Makefile | 2 +
security/apparmor/.gitignore | 5 +++
security/apparmor/Kconfig | 62 ++++++++++++++++++++++++++++++++++++++++++
security/apparmor/Makefile | 25 +++++++++++++++++
5 files changed, 95 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/.gitignore
create mode 100644 security/apparmor/Kconfig
create mode 100644 security/apparmor/Makefile

diff --git a/security/Kconfig b/security/Kconfig
index fb363cd..2f5fb0f 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -162,6 +162,7 @@ config LSM_MMAP_MIN_ADDR
source security/selinux/Kconfig
source security/smack/Kconfig
source security/tomoyo/Kconfig
+source security/apparmor/Kconfig

source security/integrity/ima/Kconfig

diff --git a/security/Makefile b/security/Makefile
index 95ecc06..8bcd805 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_KEYS) += keys/
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_SMACK) += smack
subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
+subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor

# always enable default capabilities
obj-y += commoncap.o min_addr.o
@@ -18,6 +19,7 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o
obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o
obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/built-in.o
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/built-in.o
obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o

diff --git a/security/apparmor/.gitignore b/security/apparmor/.gitignore
new file mode 100644
index 0000000..0a0a99f
--- /dev/null
+++ b/security/apparmor/.gitignore
@@ -0,0 +1,5 @@
+#
+# Generated include files
+#
+af_names.h
+capability_names.h
diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
new file mode 100644
index 0000000..01c8754
--- /dev/null
+++ b/security/apparmor/Kconfig
@@ -0,0 +1,62 @@
+config SECURITY_APPARMOR
+ bool "AppArmor support"
+ depends on SECURITY && SECURITY_NETWORK && NET && INET
+ select AUDIT
+ select SECURITY_PATH
+ select SECURITYFS
+ default 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.
+
+config SECURITY_APPARMOR_NETWORK
+ bool "AppArmor network support"
+ depends on SECURITY_APPARMOR
+ default n
+ help
+ This enables AppArmor to mediate applications network use.
+ This will enable the SECURITY_NETWORK hooks.
+
+config SECURITY_APPARMOR_BOOTPARAM_VALUE
+ int "AppArmor boot parameter default value"
+ depends on SECURITY_APPARMOR
+ range 0 1
+ default 1
+ help
+ This option sets the default value for the kernel parameter
+ 'apparmor', which allows AppArmor to be enabled or disabled
+ at boot. If this option is set to 0 (zero), the AppArmor
+ kernel parameter will default to 0, disabling AppArmor at
+ bootup. If this option is set to 1 (one), the AppArmor
+ kernel parameter will default to 1, enabling AppArmor at
+ bootup.
+
+ If you are unsure how to answer this question, answer 1.
+
+config SECURITY_APPARMOR_DISABLE
+ bool "AppArmor runtime disable"
+ depends on SECURITY_APPARMOR
+ default n
+ help
+ This option enables writing to a apparmorfs node 'disable', which
+ allows AppArmor to be disabled at runtime prior to the policy load.
+ AppArmor will then remain disabled until the next boot.
+ This option is similar to the apparmor.enabled=0 boot parameter,
+ but is to support runtime disabling of AppArmor, e.g. from
+ /sbin/init, for portability across platforms where boot
+ parameters are difficult to employ.
+
+ If you are unsure how to answer this question, answer N.
+
+config SECURITY_APPARMOR_COMPAT_24
+ bool "Enable AppArmor 2.4 compatability"
+ depends on SECURITY_APPARMOR
+ default y
+ help
+ This option enables compatability with AppArmor 2.4. It is
+ recommended if compatability with older versions of AppArmor
+ is desired.
diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
new file mode 100644
index 0000000..02d7f3c
--- /dev/null
+++ b/security/apparmor/Makefile
@@ -0,0 +1,25 @@
+# Makefile for AppArmor Linux Security Module
+#
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
+
+apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
+ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
+ resource.o sid.o file.o
+
+apparmor-$(CONFIG_SECURITY_APPARMOR_NETWORK) += net.o
+apparmor-$(CONFIG_SECURITY_APPARMOR_COMPAT_24) += apparmorfs-24.o
+
+clean-files: capability_names.h af_names.h
+
+quiet_cmd_make-caps = GEN $@
+cmd_make-caps = echo "static const char *capability_names[] = {" > $@ ; 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 >> $@ ; echo "};" >> $@
+
+quiet_cmd_make-af = GEN $@
+cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ; sed -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "s/^\#define[ \\t]\\+AF_\\([A-Z0-9_]\\+\\)[ \\t]\\+\\([0-9]\\+\\)\\(.*\\)\$$/[\\2] = \"\\1\",/p" $< | tr A-Z a-z >> $@ ; echo "};" >> $@
+
+$(obj)/capability.o : $(obj)/capability_names.h
+$(obj)/net.o : $(obj)/af_names.h
+$(obj)/capability_names.h : $(srctree)/include/linux/capability.h
+ $(call cmd,make-caps)
+$(obj)/af_names.h : $(srctree)/include/linux/socket.h
+ $(call cmd,make-af)
--
1.6.3.3

2009-11-10 16:29:51

by Pekka Enberg

[permalink] [raw]
Subject: Re: [PATCH 07/12] AppArmor: userspace interfaces

Hi John,

On Tue, Nov 10, 2009 at 6:13 PM, John Johansen
<[email protected]> wrote:
> The current apparmorfs interface is compatible with previous versions
> of AppArmor. ?The plans are to deprecate it (hence the config option
> APPARMOR_COMPAT_24) and replace it with a more sysfs style single
> entry per file interface.

We don't usually merge compatibility code to handle ABIs that were
developed out-of-tree. Why should we treat AppArmor differently?

Pekka

2009-11-10 16:44:28

by Andi Kleen

[permalink] [raw]
Subject: Re: [PATCH 07/12] AppArmor: userspace interfaces

Pekka Enberg <[email protected]> writes:

> On Tue, Nov 10, 2009 at 6:13 PM, John Johansen
> <[email protected]> wrote:
>> The current apparmorfs interface is compatible with previous versions
>> of AppArmor. ?The plans are to deprecate it (hence the config option
>> APPARMOR_COMPAT_24) and replace it with a more sysfs style single
>> entry per file interface.
>
> We don't usually merge compatibility code to handle ABIs that were
> developed out-of-tree. Why should we treat AppArmor differently?

I would say that always depends on the deployed base of the old ABI.
If there's a lot of users who would get broken I think there's a
good case for merging compat code (I don't know if that is or
isn't the case here).

A widely used distribution release with the old user land would
probably count.

-Andi
--
[email protected] -- Speaking for myself only.

2009-11-10 18:21:35

by Stephen Hemminger

[permalink] [raw]
Subject: Re: [PATCH 07/12] AppArmor: userspace interfaces

On Tue, 10 Nov 2009 17:44:27 +0100
Andi Kleen <[email protected]> wrote:

> Pekka Enberg <[email protected]> writes:
>
> > On Tue, Nov 10, 2009 at 6:13 PM, John Johansen
> > <[email protected]> wrote:
> >> The current apparmorfs interface is compatible with previous versions
> >> of AppArmor.  The plans are to deprecate it (hence the config option
> >> APPARMOR_COMPAT_24) and replace it with a more sysfs style single
> >> entry per file interface.
> >
> > We don't usually merge compatibility code to handle ABIs that were
> > developed out-of-tree. Why should we treat AppArmor differently?
>
> I would say that always depends on the deployed base of the old ABI.
> If there's a lot of users who would get broken I think there's a
> good case for merging compat code (I don't know if that is or
> isn't the case here).
>
> A widely used distribution release with the old user land would
> probably count.
>

Then the distribution can maintain a patch to add the necessary translation

It is not the upstream kernel's job to maintain compatibility with older
out of tree code.

2009-11-10 18:51:53

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 07/12] AppArmor: userspace interfaces

Pekka Enberg wrote:
> Hi John,
>
> On Tue, Nov 10, 2009 at 6:13 PM, John Johansen
> <[email protected]> wrote:
>> The current apparmorfs interface is compatible with previous versions
>> of AppArmor. The plans are to deprecate it (hence the config option
>> APPARMOR_COMPAT_24) and replace it with a more sysfs style single
>> entry per file interface.
>
> We don't usually merge compatibility code to handle ABIs that were
> developed out-of-tree. Why should we treat AppArmor differently?
>
Not necessarily saying you should. We would certainly like to support the
current interface as it will be a pain for our users if newer kernels break
abi so the user space tools don't work. And there is also that the compat
interface is the only interface currently supported. The goal was to
declare our intent to deprecate the interface and move to a new
interface interface in time.

cheers
john

2009-11-13 17:44:44

by Stephen Hemminger

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

On Tue, 10 Nov 2009 08:12:53 -0800
John Johansen <[email protected]> wrote:

> This is the newest version of the AppArmor security module it has been
> rewritten to use the security_path hooks instead of the previous vfs
> approach. The current implementation is aimed at being as semantically
> close to previous versions of AppArmor as possible while using the
> existing LSM infrastructure.

Does it fix the problem reported as the #1 failure on kernel oops:

Oops 718946 first showed up in kernel version 2.6.31-14-generic
Oops 718946 last showed up in version 2.6.31-13-generic
2.6.31 -- 512

BUG: unable to handle kernel NULL pointer dereference at 00000040
IP: [] apparmor_bprm_set_creds+0x370/0x400
*pde = 00000000
Oops: 0000 [#1] SMP
last sysfs file: /sys/devices/LNXSYSTM:00/device:00/PNP0C0A:00/power_supply/BAT1/charge_full
Modules linked in: binfmt_misc ppdev lp parport joydev snd_hda_codec_realtek snd_hda_intel snd_hda_codec snd_pcm_oss mmc_block snd_mixer_oss snd_pcm snd_seq_dummy arc4 ecb snd_seq_oss snd_seq_midi snd_rawmidi snd_seq_midi_event snd_seq ath5k acerhdf mac80211 snd_timer ath uvcvideo videodev sdhci_pci snd_seq_device psmouse sdhci v4l1_compat serio_raw cfg80211 jmb38x_ms memstick led_class snd soundcore snd_page_alloc usbhid r8169 mii fbcon tileblit font bitblit softcursor i915 drm i2c_algo_bit video output intel_agp agpgart

Pid: 3316, comm: hamachi-init Not tainted (2.6.31-10-generic #32-Ubuntu) AOA110
EIP: 0060:[] EFLAGS: 00010246 CPU: 0
EIP is at apparmor_bprm_set_creds+0x370/0x400
EAX: fffffffe EBX: dde0fe00 ECX: de59df00 EDX: dd4bfee2
ESI: 00000000 EDI: ddf73ba0 EBP: de59df44 ESP: de59deb4
DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068
Process hamachi-init (pid: 3316, ti=de59c000 task=de02d7f0 task.ti=de59c000)
Stack:
de59df00 00000000 00000000 de59ded0 c01c9e40 de5147e8 dec04080 de59deec
<0> c01c9e8f 00000000 00000000 de5147e8 00000000 de5147e8 00000000 000000d0
<0> fffffffe c06ff3a2 00000000 dd4bfee2 00000000 00000000 00000000 00000000
Call Trace:
[] ? __vma_link_rb+0x30/0x40
[] ? __vma_link+0x3f/0x80
[] ? security_bprm_set_creds+0xc/0x10
[] ? prepare_binprm+0xa1/0xf0
[] ? T.626+0x3b/0x50
[] ? do_execve+0x17e/0x2c0
[] ? strncpy_from_user+0x35/0x60
[] ? sys_execve+0x28/0x60
[] ? syscall_call+0x7/0xb
Code: 24 8b 44 24 18 e8 71 f4 ff ff 3d 00 f0 ff ff 89 c1 76 a7 0f b7 44 24 60 f6 c4 40 74 50 c7 44 24 48 a7 f3 6f c0 e9 98 fe ff ff 90 46 40 08 0f 84 e6 fe ff ff e9 d9 fe ff ff 90 8b 54 24 4c 8b
EIP: [] apparmor_bprm_set_creds+0x370/0x400 SS:ESP 0068:de59deb4
CR2: 0000000000000040
---[ end trace 203b1750ff60d177 ]---

http://kerneloops.org/guilty.php?guilty=apparmor_bprm_set_creds&version=2.6.31-release&start=2064384&end=2097151&class=oops

2009-11-13 17:58:22

by John Johansen

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

Stephen Hemminger wrote:
> On Tue, 10 Nov 2009 08:12:53 -0800
> John Johansen <[email protected]> wrote:
>
>> This is the newest version of the AppArmor security module it has been
>> rewritten to use the security_path hooks instead of the previous vfs
>> approach. The current implementation is aimed at being as semantically
>> close to previous versions of AppArmor as possible while using the
>> existing LSM infrastructure.
>
> Does it fix the problem reported as the #1 failure on kernel oops:
>
> Oops 718946 first showed up in kernel version 2.6.31-14-generic
> Oops 718946 last showed up in version 2.6.31-13-generic
> 2.6.31 -- 512

yes, and several others oops as well. I have also identified a couple other
oops that will have fixes in push #4.

thanks
john

2009-11-15 22:14:10

by David Lang

[permalink] [raw]
Subject: Re: [PATCH 07/12] AppArmor: userspace interfaces

On Tue, 10 Nov 2009, Andi Kleen wrote:

> Pekka Enberg <[email protected]> writes:
>
>> On Tue, Nov 10, 2009 at 6:13 PM, John Johansen
>> <[email protected]> wrote:
>>> The current apparmorfs interface is compatible with previous versions
>>> of AppArmor. ?The plans are to deprecate it (hence the config option
>>> APPARMOR_COMPAT_24) and replace it with a more sysfs style single
>>> entry per file interface.
>>
>> We don't usually merge compatibility code to handle ABIs that were
>> developed out-of-tree. Why should we treat AppArmor differently?
>
> I would say that always depends on the deployed base of the old ABI.
> If there's a lot of users who would get broken I think there's a
> good case for merging compat code (I don't know if that is or
> isn't the case here).
>
> A widely used distribution release with the old user land would
> probably count.

ubuntu has shipped with AppArmor for the last few releases.

David Lang

2009-11-15 22:14:36

by David Lang

[permalink] [raw]
Subject: Re: [PATCH 07/12] AppArmor: userspace interfaces

On Tue, 10 Nov 2009, Stephen Hemminger wrote:

> On Tue, 10 Nov 2009 17:44:27 +0100
> Andi Kleen <[email protected]> wrote:
>
>> Pekka Enberg <[email protected]> writes:
>>
>>> On Tue, Nov 10, 2009 at 6:13 PM, John Johansen
>>> <[email protected]> wrote:
>>>> The current apparmorfs interface is compatible with previous versions
>>>> of AppArmor.  The plans are to deprecate it (hence the config option
>>>> APPARMOR_COMPAT_24) and replace it with a more sysfs style single
>>>> entry per file interface.
>>>
>>> We don't usually merge compatibility code to handle ABIs that were
>>> developed out-of-tree. Why should we treat AppArmor differently?
>>
>> I would say that always depends on the deployed base of the old ABI.
>> If there's a lot of users who would get broken I think there's a
>> good case for merging compat code (I don't know if that is or
>> isn't the case here).
>>
>> A widely used distribution release with the old user land would
>> probably count.
>>
>
> Then the distribution can maintain a patch to add the necessary translation

that works for future releases, but not for past releases.

David Lang

> It is not the upstream kernel's job to maintain compatibility with older
> out of tree code.
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at http://www.tux.org/lkml/
>

2009-11-20 17:39:52

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

Hello.

Sorry for late response.
I browsed apparmorfs-24.c apparmorfs.c audit.c capability.c context.c domain.c .
Comments are shown below.



> static int aa_audit_base(int type, struct aa_profile *profile,
> struct aa_audit *sa, struct audit_context *audit_cxt,
> void (*cb) (struct audit_buffer *, void *))
(...snipped...)
> if (profile && PROFILE_KILL(profile) && type == AUDIT_APPARMOR_DENIED)
> type = AUDIT_APPARMOR_KILL;

PROFILE_KILL(profile) includes profile != NULL checks.
Are you doing

profile && PROFILE_KILL(profile)

in order to ignore aa_g_profile_mode == APPARMOR_KILL if profile == NULL?



> int aa_restore_previous_profile(u64 token)
> {
> struct aa_task_context *cxt;
> struct cred *new = prepare_creds();
> if (!new)
> return -ENOMEM;
>
> cxt = new->security;
> if (cxt->sys.token != token) {
> abort_creds(new);

I think simply returning -EACCES when trying to escape from some profile
gives hijacked process chances to do brute force attack.
Don't you need to kill the current process?

> return -EACCES;
> }



> static struct aa_profile *x_to_profile(struct aa_namespace *ns,
> struct aa_profile *profile,
> const char *name, u16 xindex)
(...snipped...)
> case AA_X_TABLE:
> if (index > profile->file.trans.size) {

profile->file.trans.table[0] is not permitted if profile->file.trans.size == 0,
is it?
Did you mean index >= profile->file.trans.size ?

> AA_ERROR("Invalid named transition\n");
> return ERR_PTR(-EACCES);
> }
> name = profile->file.trans.table[index];
(...snipped...)
> } else if (*name == ':') {
> /* switching namespace */
> const char *ns_name = name + 1;
> name = xname = ns_name + strlen(ns_name) + 1;
> if (!*xname)

Isn't *xname undefined because it is beyond '\0'?

> /* no name so use profile name */
> xname = profile->fqname;

(...snipped...)

> } else if (*name == '@') {
> /* TODO: variable support */
>

You want "continue;" here in order to avoid doing strstr(NULL, "//")
inside aa_find_profile().

> } else {



> int apparmor_bprm_set_creds(struct linux_binprm *bprm)
> {
> struct aa_task_context *cxt;
> struct aa_profile *profile, *new_profile = NULL;
> struct aa_namespace *ns;
> char *buffer = NULL;
> unsigned int state = DFA_START;
> struct path_cond cond = {
> bprm->file->f_path.dentry->d_inode->i_uid,
> bprm->file->f_path.dentry->d_inode->i_mode
> };
> struct aa_audit_file sa = {
> .base.operation = "exec",
> .base.gfp_mask = GFP_KERNEL,
> .request = MAY_EXEC,
> .cond = &cond,
> };
>
> sa.base.error = cap_bprm_set_creds(bprm);
> if (sa.base.error)
> return sa.base.error;
>
> if (bprm->cred_prepared)
> return 0;
>
> cxt = bprm->cred->security;
> BUG_ON(!cxt);
>
> profile = aa_confining_profile(cxt->sys.profile);
> ns = profile->ns;

aa_confining_profile() may return NULL.
According to apparmor-kermic tree, it is

ns = cxt->sys.profile->ns;

>
> /* buffer freed below, name is pointer inside of buffer */
> sa.base.error = aa_get_name(&bprm->file->f_path, 0, &buffer,
> (char **)&sa.name);
> if (sa.base.error) {
> if (!profile || profile->flags & PFLAG_IX_ON_NAME_ERROR)
> sa.base.error = 0;
> sa.base.info = "Exec failed name resolution";
> sa.name = bprm->filename;
> goto audit;
> }
>
> if (!profile) {
> /* unconfined task - attach profile if one matches */
> new_profile = aa_sys_find_attach(&ns->base, sa.name);
> if (!new_profile)
> goto cleanup;
> goto apply;
> } else if (cxt->sys.onexec) {
> /*
> * onexec permissions are stored in a pair, rewalk the
> * dfa to get start of the exec path match.
> */
> sa.perms = change_profile_perms(profile, cxt->sys.onexec->ns,
> sa.name, &state);
> state = aa_dfa_null_transition(profile->file.dfa, state);
> }
> sa.perms = aa_str_perms(profile->file.dfa, state, sa.name, &cond, NULL);>
> if (cxt->sys.onexec && sa.perms.allowed & AA_MAY_ONEXEC) {
> new_profile = cxt->sys.onexec;
> cxt->sys.onexec = NULL;
> sa.base.info = "change_profile onexec";
> } else if (sa.perms.allowed & MAY_EXEC) {
> new_profile = x_to_profile(ns, profile, sa.name,
> sa.perms.xindex);
> if (IS_ERR(new_profile)) {
> if (sa.perms.xindex & AA_X_INHERIT) {
> /* (p|c|n)ix - don't change profile */
> sa.base.info = "ix fallback";
> goto x_clear;
> } else if (sa.perms.xindex & AA_X_UNCONFINED) {
> new_profile = aa_get_profile(ns->unconfined);
> sa.base.info = "ux fallback";

IS_ERR(new_profile) is true but new_profile == NULL is false.
What I worry is that you sometimes embed error values into pointer but you
are sometimes checking only NULL.

> } else {
> sa.base.error = PTR_ERR(new_profile);
> if (sa.base.error == -ENOENT)
> sa.base.info = "profile not found";
> new_profile = NULL;
> }
> }
> } else if (PROFILE_COMPLAIN(profile)) {
> new_profile = aa_alloc_null_profile(profile, 0);
> sa.base.error = -EACCES;
> if (!new_profile)
> sa.base.error = -ENOMEM;
> sa.name2 = new_profile->fqname;

This will oops if new_profile == NULL. Please fix apparmor-karmic as well.

> sa.perms.xindex |= AA_X_UNSAFE;
> } else {
> sa.base.error = -EACCES;
> }
>
> if (!new_profile)
> goto audit;

You want to do

if (!new_profile || IS_ERR(new_profile))

rather than

if (!new_profile)

Please fix apparmor-karmic as well.

>
> if (profile == new_profile) {
> aa_put_profile(new_profile);

aa_put_profile() with error pointer, which will be fixed by above change.

> goto audit;
> }
>
> if (bprm->unsafe & LSM_UNSAFE_SHARE) {
> /* FIXME: currently don't mediate shared state */
> ;
> }
>
> if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
> sa.base.error = aa_may_change_ptraced_domain(current,
> new_profile);

aa_may_change_ptraced_domain() with error pointer, which will be fixed by
above change.

> if (sa.base.error)
> goto audit;
> }
>
> /* Determine if secure exec is needed.
> * Can be at this point for the following reasons:
> * 1. unconfined switching to confined
> * 2. confined switching to different confinement
> * 3. confined switching to unconfined
> *
> * Cases 2 and 3 are marked as requiring secure exec
> * (unless policy specified "unsafe exec")
> *
> * bprm->unsafe is used to cache the AA_X_UNSAFE permission
> * to avoid having to recompute in secureexec
> */
> if (!(sa.perms.xindex & AA_X_UNSAFE))
> bprm->unsafe |= AA_SECURE_X_NEEDED;
>
> apply:
> sa.name2 = new_profile->fqname;

error pointer, which will be fixed by above change.

> /* When switching namespace ensure its part of audit message */
> if (new_profile->ns != ns)
> sa.name3 = new_profile->ns->base.name;
>
> /* when transitioning profiles clear unsafe personality bits */
> bprm->per_clear |= PER_CLEAR_ON_SETID;
>
> aa_put_profile(cxt->sys.profile);
> /* transfer new profile reference will be released when cxt is freed */
> cxt->sys.profile = new_profile;
>
> x_clear:
> aa_put_profile(cxt->sys.previous);
> aa_put_profile(cxt->sys.onexec);
> cxt->sys.previous = NULL;
> cxt->sys.onexec = NULL;
> cxt->sys.token = 0;
>
> audit:
> sa.base.error = aa_audit_file(profile, &sa);

aa_audit_file() might return 0 if PROFILE_COMPLAIN(profile) == true even if
sa.base.error != 0 . I think regarding execve(), we should not ignore errors
like -EACCES, -ENOMEM etc. if something went wrong before auditing.
Otherwise, current process might continue execve() with unexpected profile.

>
> cleanup:
> kfree(buffer);
>
> return sa.base.error;
> }



> int aa_change_hat(const char *hat_name, u64 token, int permtest)
(...snipped...)
> /* freed below */
> name = new_compound_name(root->fqname, hat_name);
>

Audit log lacks "name=%s" part if name == NULL.

> sa.name = name;
> sa.base.info = "hat not found";
> sa.base.error = -ENOENT;



> int aa_change_profile(const char *ns_name, const char *fqname, int onexec,
> int permtest)
(...snipped...)
> /* released below */
> target = aa_find_profile(ns, fqname);
> if (!target) {
> sa.base.info = "profile not found";
> sa.base.error = -ENOENT;
> if (permtest || !PROFILE_COMPLAIN(profile))
> goto audit;
> /* release below */
> target = aa_alloc_null_profile(profile, 0);

aa_alloc_null_profile() will oops if profile == NULL.

2009-11-21 05:28:17

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

Regarding file.c ipc.c lib.c lsm.c



You can use container_of() inside callback functions to avoid "void *".

> int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
> void (*cb) (struct audit_buffer *, void *))

int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
void (*cb) (struct audit_buffer *, struct aa_audit *))

> static int aa_audit_base(int type, struct aa_profile *profile,
> struct aa_audit *sa, struct audit_context *audit_cxt,
> void (*cb) (struct audit_buffer *, void *))

static int aa_audit_base(int type, struct aa_profile *profile,
struct aa_audit *sa, struct audit_context *audit_cxt,
void (*cb) (struct audit_buffer *, struct aa_audit *))

> void file_audit_cb(struct audit_buffer *ab, void *va)
> {
> struct aa_audit_file *sa = va;

void file_audit_cb(struct audit_buffer *ab, struct aa_audit *va)
{
struct aa_audit_file *sa = container_of(va, struct aa_audit_file, base);

> int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa)
> (...snipped...)
> return aa_audit(type, profile, (struct aa_audit *)sa, file_audit_cb);

return aa_audit(type, profile, &sa->base, file_audit_cb);

> }

> static void audit_cb(struct audit_buffer *ab, void *va)
> {
> struct aa_audit_ptrace *sa = va;

static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
{
struct aa_audit_ptrace *sa = container_of(va, struct aa_audit_ptrace,
base);

> static int aa_audit_ptrace(struct aa_profile *profile,
> struct aa_audit_ptrace *sa)
> {
> return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
> audit_cb);

return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa->base, audit_cb);



> int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
> struct path *new_dir, struct dentry *new_dentry)
(.,..snipped...)
> unsigned int state;
(.,..snipped...)
> sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, &cond,
> &state);

"state" remains uninitialized if profile->file.dfa == NULL.
Are you sure profile->file.dfa != NULL ?



> char *aa_strchrnul(const char *s, int c)
> {
> for (; *s != (char)c && *s != '\0'; ++s)
> ;
> return (char *)s;
> }

Only fqname_subname() calls aa_strchrnul() and
fqname_subname() returns NULL if aa_strchrnul() returns '\0'.
You can use strchr() instead.

> static const char *fqname_subname(const char *name)
> {
> char *split;
> /* check for namespace which begins with a : and ends with : or \0 */
> name = strstrip((char *)name);
> if (*name == ':') {
> split = aa_strchrnul(name + 1, ':');
> if (*split == '\0')
> return NULL;

split = strchr(name + 1, ':');
if (!split)
return NULL;

> name = strstrip(split + 1);
> }
> for (split = strstr(name, "//"); split; split = strstr(name, "//"))
> name = split + 2;
>
> return name;
> }



> char *aa_split_name_from_ns(char *args, char **ns_name)
> {
> char *name = strstrip(args);
>
> *ns_name = NULL;
> if (args[0] == ':') {
> char *split = strstrip(strchr(&args[1], ':'));
>
> if (!split)
> return NULL;


strchr() returns NULL if not found, and strstrip(NULL) will do strlen(NULL).
strstrip() never returns NULL. Did you mean

char *split = strchr(&args[1], ':');

if (!split)
return NULL;
split = strstrip(split);

?

>
> *split = 0;
> *ns_name = &args[1];
> name = strstrip(split + 1);
> }
> if (*name == 0)
> name = NULL;
>
> return name;
> }



> static int apparmor_sysctl(struct ctl_table *table, int op)
This hook will be removed.

> char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen)
This function will no longer be needed.

> int aa_pathstr_perm(struct aa_profile *profile, const char *op,
> const char *name, u16 request, struct path_cond *cond)
This function will no longer be needed.



> static int apparmor_file_permission(struct file *file, int mask)
> {
> /*
> * TODO: cache profiles that have revalidated?
> */
> struct aa_file_cxt *fcxt = file->f_security;
> struct aa_profile *profile, *fprofile = fcxt->profile;
> int error = 0;
>
> if (!fprofile || !file->f_path.mnt ||
> !mediated_filesystem(file->f_path.dentry->d_inode))
> return 0;
>
> profile = aa_current_profile();
>
> #ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
> /*
> * AppArmor <= 2.4 revalidates files at access time instead
> * of at exec.
> */
> if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
> error = aa_file_perm(profile, "file_perm", file, mask);
> #endif
>
> return error;
> }

Why need to call this function if CONFIG_SECURITY_APPARMOR_COMPAT_24=n ?
I think we can do

> static struct security_operations apparmor_ops = {
(...snipped...)

#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24

> .file_permission = apparmor_file_permission,

#endif

(...snipped...)
> }



> int aa_alloc_default_namespace(void)

This function could be declared with __init attribute.



> static int __init apparmor_init(void)
(...snipped...)
> error = set_init_cxt();
> if (error) {
> AA_ERROR("Failed to set context on init task\n");
> goto alloc_out;

This should be

goto register_security_out;

in order to call aa_free_default_namespace().

> }

2009-11-22 11:49:50

by Tetsuo Handa

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

And the rest of files...



Regarding match.c

Why not to start YYTD_ID_something from 0 so that we can avoid "- 1" in
dfa->tables[table->td_id - 1] ? I think you can do "- 1" at

th.td_id = be16_to_cpu(*(u16 *) (blob));

.



> int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
> {
(...snipped...)
> fail:
> for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
> free_table(dfa->tables[i]);
> dfa->tables[i] = NULL;
> }

This function is called by only aa_unpack_dfa(), and aa_unpack_dfa() calls
aa_match_free(). Thus, you don't need to call free_table() here.

> return error;
> }



Regarding net.c

> static void audit_cb(struct audit_buffer *ab, void *va)
> {
> struct aa_audit_net *sa = va;

static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
{
struct aa_audit_net *sa = container_of(va, struct aa_audit_net, base);

> static int aa_audit_net(struct aa_profile *profile, struct aa_audit_net *sa)
> {
(...snipped...)
> return aa_audit(type, profile, (struct aa_audit *)sa, audit_cb);

return aa_audit(type, profile, &sa->base, audit_cb);

> }



Regarding policy.c

> struct aa_namespace *alloc_aa_namespace(const char *name)

This function could be "static". Please try "make namespacecheck".



> struct aa_namespace *aa_prepare_namespace(const char *name)

This function could be "static".

> {
> struct aa_namespace *ns;
>
> write_lock(&ns_list_lock);
> if (name)
> /* released by caller */
> ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
> else
> /* released by caller */
> ns = aa_get_namespace(default_namespace);

alloc_aa_namespace() returns NULL if name == NULL.
If it is intended behavior, you may do like

else {
/* released by caller */
ns = aa_get_namespace(default_namespace);
write_unlock(&ns_list_lock);
return ns;
}

> if (!ns) {
> struct aa_namespace *new_ns;
> write_unlock(&ns_list_lock);
> new_ns = alloc_aa_namespace(name);
> if (!new_ns)
> return NULL;
> write_lock(&ns_list_lock);
> /* test for race when new_ns was allocated */
> ns = __aa_find_namespace(&ns_list, name);
> if (!ns) {
> list_add(&new_ns->base.list, &ns_list);
> /* add list ref */
> ns = aa_get_namespace(new_ns);
> } else {
> /* raced so free the new one */
> free_aa_namespace(new_ns);
> /* get reference on namespace */
> aa_get_namespace(ns);
> }
> }
> write_unlock(&ns_list_lock);
>
> /* return ref */
> return ns;
> }



> void __aa_replace_profile(struct aa_profile *old,
> struct aa_profile *new)

This function could be "static".



> void __aa_profile_list_release(struct list_head *head)

This function could be "static".



> void __aa_remove_namespace(struct aa_namespace *ns)

This function could be "static".

> {
> struct aa_profile *unconfined = ns->unconfined;
> /* remove ns from namespace list */
> list_del_init(&ns->base.list);
>
> /*
> * break the ns, unconfined profile cyclic reference and forward
> * all new unconfined profiles requests to the default namespace
> * This will result in all confined tasks that have a profile
> * being removed inheriting the default->unconfined profile.
> */
> ns->unconfined = aa_get_profile(default_namespace->unconfined);
> __aa_profile_list_release(&ns->base.profiles);
> /* release original ns->unconfined ref */
> aa_put_profile(unconfined);
> /* release ns->base.list ref, from removal above */
> aa_put_namespace(ns);

aa_put_profile() and aa_put_namespace() may call write_lock() inside
free_aa_profile(). Are you sure that these calls do not dead lock?

> }



> ssize_t aa_interface_remove_profiles(char *name, size_t size)
(...snipped...)
> write_lock(&ns_list_lock);
> if (name[0] == ':') {
> char *ns_name;
> name = aa_split_name_from_ns(name, &ns_name);
> /* released below */
> ns = aa_get_namespace(__aa_find_namespace(&ns_list, ns_name));

aa_split_name_from_ns() may set ns_name to NULL but __aa_find_namespace() can't
handle ns_name == NULL case. I think you should check ns_name != NULL.



Regarding policy_unpack.c

Please use bool for functions that return 0 or 1.



> static void audit_cb(struct audit_buffer *ab, void *va)
> {
> struct aa_audit_iface *sa = va;

static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
{
struct aa_audit_iface *sa = container_of(va, struct aa_audit_iface,
base);



> static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
(...snipped...)
> for (i = 0; i < size; i++) {
> char *tmp;
> if (!unpack_dynstring(e, &tmp, NULL))
> goto fail;
> /*
> * note: strings beginning with a : have an embedded
> * \0 seperating the profile ns name from the profile
> * name
> */
> profile->file.trans.table[i] = tmp;

unpack_dynstring() returns string duplicated by kstrdup(). Thus, "tmp" can't
have an embedded \0 seperating the profile ns name from the profile name
even if tmp[0] == ':' is true, can it?

> }



Regarding resource.c

> static void audit_cb(struct audit_buffer *ab, void *va)
> {
> struct aa_audit_resource *sa = va;

static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
{
struct aa_audit_resource *sa = container_of(va, struct aa_audit_resource,
base);



> static int aa_audit_resource(struct aa_profile *profile,
> struct aa_audit_resource *sa)
> {
> return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
> audit_cb);

return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa->base, audit_cb);

> }



Regarding sid.c

> int aa_add_sid_profile(u32 sid, struct aa_profile *profile)

This function is not used.



> int aa_replace_sid_profile(u32 sid, struct aa_profile *profile)

This function is not used.



> struct aa_profile *aa_get_sid_profile(u32 sid)

This function is not used.

2009-11-23 10:10:40

by John Johansen

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

Tetsuo Handa wrote:
> Hello.
>
> Sorry for late response.
np. we are all busy and any time taken to review code is greatly appreciated

> I browsed apparmorfs-24.c apparmorfs.c audit.c capability.c context.c domain.c .
> Comments are shown below.
>
>
>
>> static int aa_audit_base(int type, struct aa_profile *profile,
>> struct aa_audit *sa, struct audit_context *audit_cxt,
>> void (*cb) (struct audit_buffer *, void *))
> (...snipped...)
>> if (profile && PROFILE_KILL(profile) && type == AUDIT_APPARMOR_DENIED)
>> type = AUDIT_APPARMOR_KILL;
>
> PROFILE_KILL(profile) includes profile != NULL checks.
> Are you doing
>
> profile && PROFILE_KILL(profile)
>
> in order to ignore aa_g_profile_mode == APPARMOR_KILL if profile == NULL?
>
yes. The profile kill flag should not be set unless there is a profile
confining the task. This is confusing and really should be reworked, by
either renaming PROFILE_KILL and adding a comment here, or reworking the
PROFILE_KILL check, and its callers. In any case I need to go through
and document which functions require filtered vs. unfiltered profiles
(ie. profile != NULL) I'll update this for the next posting.

>
>
>> int aa_restore_previous_profile(u64 token)
>> {
>> struct aa_task_context *cxt;
>> struct cred *new = prepare_creds();
>> if (!new)
>> return -ENOMEM;
>>
>> cxt = new->security;
>> if (cxt->sys.token != token) {
>> abort_creds(new);
>
> I think simply returning -EACCES when trying to escape from some profile
> gives hijacked process chances to do brute force attack.
> Don't you need to kill the current process?
>
>> return -EACCES;
>> }
>
>
correct and we do. It is done by the caller (aa_change_hat()) by setting the
kill flag in the audit structure.
} else if (previous_profile) {
sa.name = previous_profile->fqname;
sa.base.error = aa_restore_previous_profile(token);
sa.perms.kill = AA_MAY_CHANGEHAT;

>
>> static struct aa_profile *x_to_profile(struct aa_namespace *ns,
>> struct aa_profile *profile,
>> const char *name, u16 xindex)
> (...snipped...)
>> case AA_X_TABLE:
>> if (index > profile->file.trans.size) {
>
> profile->file.trans.table[0] is not permitted if profile->file.trans.size == 0,
> is it?
No it isn't.
> Did you mean index >= profile->file.trans.size ?
>
No, a file.trans.size == 0 means there are no transition entries, in which case the
type should not be AA_X_TABLE. It is a consistency check that should never occur
if the user space tools properly generate the policy. This check could, and probably
should be moved to a late pass in the policy unpack, so the check is only ever done
once for a given profile.


>> AA_ERROR("Invalid named transition\n");
>> return ERR_PTR(-EACCES);
>> }
>> name = profile->file.trans.table[index];
> (...snipped...)
>> } else if (*name == ':') {
>> /* switching namespace */
>> const char *ns_name = name + 1;
>> name = xname = ns_name + strlen(ns_name) + 1;
>> if (!*xname)
>
> Isn't *xname undefined because it is beyond '\0'?
>
No, however this needs to be better documented, and perhaps pulled out
into its own function. The encoding of the names:
if there is a namespace the general case is
:<namespace>\0<name>\0
if there is a namespace and the name is not specified, it becomes
:<namespace>\0\0
else if the name does not begin with a : it is just a name
<name>\0


>> /* no name so use profile name */
>> xname = profile->fqname;
>
> (...snipped...)
>
>> } else if (*name == '@') {
>> /* TODO: variable support */
>>
>
> You want "continue;" here in order to avoid doing strstr(NULL, "//")
> inside aa_find_profile().
>
yep, thanks for catching that

>> } else {
>
>
>
>> int apparmor_bprm_set_creds(struct linux_binprm *bprm)
>> {
>> struct aa_task_context *cxt;
>> struct aa_profile *profile, *new_profile = NULL;
>> struct aa_namespace *ns;
>> char *buffer = NULL;
>> unsigned int state = DFA_START;
>> struct path_cond cond = {
>> bprm->file->f_path.dentry->d_inode->i_uid,
>> bprm->file->f_path.dentry->d_inode->i_mode
>> };
>> struct aa_audit_file sa = {
>> .base.operation = "exec",
>> .base.gfp_mask = GFP_KERNEL,
>> .request = MAY_EXEC,
>> .cond = &cond,
>> };
>>
>> sa.base.error = cap_bprm_set_creds(bprm);
>> if (sa.base.error)
>> return sa.base.error;
>>
>> if (bprm->cred_prepared)
>> return 0;
>>
>> cxt = bprm->cred->security;
>> BUG_ON(!cxt);
>>
>> profile = aa_confining_profile(cxt->sys.profile);
>> ns = profile->ns;
>
> aa_confining_profile() may return NULL.
> According to apparmor-kermic tree, it is
>
> ns = cxt->sys.profile->ns;
>
yep, I introduced this error when reworking the profile filtering, while
cleaning up the removed profile checks. I really should have done it
as a separate patch, and I only caught it after I pushed this patch set.
It is already fixed for the next push.

>> /* buffer freed below, name is pointer inside of buffer */
>> sa.base.error = aa_get_name(&bprm->file->f_path, 0, &buffer,
>> (char **)&sa.name);
>> if (sa.base.error) {
>> if (!profile || profile->flags & PFLAG_IX_ON_NAME_ERROR)
>> sa.base.error = 0;
>> sa.base.info = "Exec failed name resolution";
>> sa.name = bprm->filename;
>> goto audit;
>> }
>>
>> if (!profile) {
>> /* unconfined task - attach profile if one matches */
>> new_profile = aa_sys_find_attach(&ns->base, sa.name);
>> if (!new_profile)
>> goto cleanup;
>> goto apply;
>> } else if (cxt->sys.onexec) {
>> /*
>> * onexec permissions are stored in a pair, rewalk the
>> * dfa to get start of the exec path match.
>> */
>> sa.perms = change_profile_perms(profile, cxt->sys.onexec->ns,
>> sa.name, &state);
>> state = aa_dfa_null_transition(profile->file.dfa, state);
>> }
>> sa.perms = aa_str_perms(profile->file.dfa, state, sa.name, &cond, NULL);>
>> if (cxt->sys.onexec && sa.perms.allowed & AA_MAY_ONEXEC) {
>> new_profile = cxt->sys.onexec;
>> cxt->sys.onexec = NULL;
>> sa.base.info = "change_profile onexec";
>> } else if (sa.perms.allowed & MAY_EXEC) {
>> new_profile = x_to_profile(ns, profile, sa.name,
>> sa.perms.xindex);
>> if (IS_ERR(new_profile)) {
>> if (sa.perms.xindex & AA_X_INHERIT) {
>> /* (p|c|n)ix - don't change profile */
>> sa.base.info = "ix fallback";
>> goto x_clear;
>> } else if (sa.perms.xindex & AA_X_UNCONFINED) {
>> new_profile = aa_get_profile(ns->unconfined);
>> sa.base.info = "ux fallback";
>
> IS_ERR(new_profile) is true but new_profile == NULL is false.
> What I worry is that you sometimes embed error values into pointer but you
> are sometimes checking only NULL.
>
Right, the use of ERR_PTR is limited but it is easy to mess up, and/or not follow
and maintenance could be problematic. It might be best to rework so none of
the functions return ERR_PTR, avoiding any potential problems here.

>> } else {
>> sa.base.error = PTR_ERR(new_profile);
>> if (sa.base.error == -ENOENT)
>> sa.base.info = "profile not found";
>> new_profile = NULL;
>> }
>> }
>> } else if (PROFILE_COMPLAIN(profile)) {
>> new_profile = aa_alloc_null_profile(profile, 0);
>> sa.base.error = -EACCES;
>> if (!new_profile)
>> sa.base.error = -ENOMEM;
>> sa.name2 = new_profile->fqname;
>
> This will oops if new_profile == NULL. Please fix apparmor-karmic as well.
>
ouch :(, thanks again

>> sa.perms.xindex |= AA_X_UNSAFE;
>> } else {
>> sa.base.error = -EACCES;
>> }
>>
>> if (!new_profile)
>> goto audit;
>
> You want to do
>
> if (!new_profile || IS_ERR(new_profile))
>
> rather than
>
> if (!new_profile)
>
> Please fix apparmor-karmic as well.
>
Well it shouldn't ever get to this code with an ERR_PTR, it is handled by
new_profile = x_to_profile(ns, profile, sa.name,
sa.perms.xindex);
if (IS_ERR(new_profile)) {
above, but it is too easy to miss this, or break in the future so I will
rework to drop the use of ERR_PTR with profile.

>> if (profile == new_profile) {
>> aa_put_profile(new_profile);
>
> aa_put_profile() with error pointer, which will be fixed by above change.
>
dito

>> goto audit;
>> }
>>
>> if (bprm->unsafe & LSM_UNSAFE_SHARE) {
>> /* FIXME: currently don't mediate shared state */
>> ;
>> }
>>
>> if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) {
>> sa.base.error = aa_may_change_ptraced_domain(current,
>> new_profile);
>
> aa_may_change_ptraced_domain() with error pointer, which will be fixed by
> above change.
>
dito

>> if (sa.base.error)
>> goto audit;
>> }
>>
>> /* Determine if secure exec is needed.
>> * Can be at this point for the following reasons:
>> * 1. unconfined switching to confined
>> * 2. confined switching to different confinement
>> * 3. confined switching to unconfined
>> *
>> * Cases 2 and 3 are marked as requiring secure exec
>> * (unless policy specified "unsafe exec")
>> *
>> * bprm->unsafe is used to cache the AA_X_UNSAFE permission
>> * to avoid having to recompute in secureexec
>> */
>> if (!(sa.perms.xindex & AA_X_UNSAFE))
>> bprm->unsafe |= AA_SECURE_X_NEEDED;
>>
>> apply:
>> sa.name2 = new_profile->fqname;
>
> error pointer, which will be fixed by above change.
>
dito

>> /* When switching namespace ensure its part of audit message */
>> if (new_profile->ns != ns)
>> sa.name3 = new_profile->ns->base.name;
>>
>> /* when transitioning profiles clear unsafe personality bits */
>> bprm->per_clear |= PER_CLEAR_ON_SETID;
>>
>> aa_put_profile(cxt->sys.profile);
>> /* transfer new profile reference will be released when cxt is freed */
>> cxt->sys.profile = new_profile;
>>
>> x_clear:
>> aa_put_profile(cxt->sys.previous);
>> aa_put_profile(cxt->sys.onexec);
>> cxt->sys.previous = NULL;
>> cxt->sys.onexec = NULL;
>> cxt->sys.token = 0;
>>
>> audit:
>> sa.base.error = aa_audit_file(profile, &sa);
>
> aa_audit_file() might return 0 if PROFILE_COMPLAIN(profile) == true even if
> sa.base.error != 0 . I think regarding execve(), we should not ignore errors
> like -EACCES, -ENOMEM etc. if something went wrong before auditing.
> Otherwise, current process might continue execve() with unexpected profile.
>
true enough, it should only be EPERM, EACCES

>> cleanup:
>> kfree(buffer);
>>
>> return sa.base.error;
>> }
>
>
>
>> int aa_change_hat(const char *hat_name, u64 token, int permtest)
> (...snipped...)
>> /* freed below */
>> name = new_compound_name(root->fqname, hat_name);
>>
>
> Audit log lacks "name=%s" part if name == NULL.
>
hrmm, yeah that is less than ideal, will fix

>> sa.name = name;
>> sa.base.info = "hat not found";
>> sa.base.error = -ENOENT;
>
>
>
>> int aa_change_profile(const char *ns_name, const char *fqname, int onexec,
>> int permtest)
> (...snipped...)
>> /* released below */
>> target = aa_find_profile(ns, fqname);
>> if (!target) {
>> sa.base.info = "profile not found";
>> sa.base.error = -ENOENT;
>> if (permtest || !PROFILE_COMPLAIN(profile))
>> goto audit;
>> /* release below */
>> target = aa_alloc_null_profile(profile, 0);
>
> aa_alloc_null_profile() will oops if profile == NULL.

right this is actually caught by the !PROFILE_COMPLAIN(profile) part of
>> if (permtest || !PROFILE_COMPLAIN(profile))
>> goto audit;
immediately above but that is less than obvious and needs to be documented.

thanks again
john

2009-11-23 10:10:50

by John Johansen

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

Tetsuo Handa wrote:
> And the rest of files...
>
>
>
> Regarding match.c
>
> Why not to start YYTD_ID_something from 0 so that we can avoid "- 1" in
> dfa->tables[table->td_id - 1] ? I think you can do "- 1" at
>
Right this is a legacy bit, to make a long story short they exactly match
the Flex table mappings which is unnecessary as we explicitly are not
compatible with Flex in other ways. It will probably wait until after
the next push as I am looking at accept state cleanup for the dfa as well.

> th.td_id = be16_to_cpu(*(u16 *) (blob));
that is a possibility as long as it got a good comment to explain what
is going on.

>
> .
>
>
>
>> int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
>> {
> (...snipped...)
>> fail:
>> for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
>> free_table(dfa->tables[i]);
>> dfa->tables[i] = NULL;
>> }
>
> This function is called by only aa_unpack_dfa(), and aa_unpack_dfa() calls
> aa_match_free(). Thus, you don't need to call free_table() here.
>
Hrmm, yeah it needs to be reworked, I don't particularly like returning a
partial struct to have it cleaned up later. I think it might be better
to rework aa_unpack_dfa, dropping the aa_match_free and moving the verify
call into the unpack routine, and the above for loop can be replaced
by aa_match_free


>> return error;
>> }
>
>
>
> Regarding net.c
>
>> static void audit_cb(struct audit_buffer *ab, void *va)
>> {
>> struct aa_audit_net *sa = va;
>
> static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {
> struct aa_audit_net *sa = container_of(va, struct aa_audit_net, base);
>
>> static int aa_audit_net(struct aa_profile *profile, struct aa_audit_net *sa)
>> {
> (...snipped...)
>> return aa_audit(type, profile, (struct aa_audit *)sa, audit_cb);
>
> return aa_audit(type, profile, &sa->base, audit_cb);
>
>> }
>
>
yep, thanks

>
> Regarding policy.c
>
>> struct aa_namespace *alloc_aa_namespace(const char *name)
>
> This function could be "static". Please try "make namespacecheck".
>
will do

>
>
>> struct aa_namespace *aa_prepare_namespace(const char *name)
>
> This function could be "static".
>
>> {
>> struct aa_namespace *ns;
>>
>> write_lock(&ns_list_lock);
>> if (name)
>> /* released by caller */
>> ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
>> else
>> /* released by caller */
>> ns = aa_get_namespace(default_namespace);
>
> alloc_aa_namespace() returns NULL if name == NULL.
> If it is intended behavior, you may do like
>
> else {
> /* released by caller */
> ns = aa_get_namespace(default_namespace);
> write_unlock(&ns_list_lock);
> return ns;
> }
>
well at this point name != NULL because in that case ns == default_namespace,
but it should be documented that name can not be null here.

In general I am not happy with prepare_namespace and will take another look
at this.

>> if (!ns) {
>> struct aa_namespace *new_ns;
>> write_unlock(&ns_list_lock);
>> new_ns = alloc_aa_namespace(name);
>> if (!new_ns)
>> return NULL;
>> write_lock(&ns_list_lock);
>> /* test for race when new_ns was allocated */
>> ns = __aa_find_namespace(&ns_list, name);
>> if (!ns) {
>> list_add(&new_ns->base.list, &ns_list);
>> /* add list ref */
>> ns = aa_get_namespace(new_ns);
>> } else {
>> /* raced so free the new one */
>> free_aa_namespace(new_ns);
>> /* get reference on namespace */
>> aa_get_namespace(ns);
>> }
>> }
>> write_unlock(&ns_list_lock);
>>
>> /* return ref */
>> return ns;
>> }
>
>
>
>> void __aa_replace_profile(struct aa_profile *old,
>> struct aa_profile *new)
>
> This function could be "static".
>
>
>
>> void __aa_profile_list_release(struct list_head *head)
>
> This function could be "static".
>
>
>
>> void __aa_remove_namespace(struct aa_namespace *ns)
>
> This function could be "static".
>
>> {
>> struct aa_profile *unconfined = ns->unconfined;
>> /* remove ns from namespace list */
>> list_del_init(&ns->base.list);
>>
>> /*
>> * break the ns, unconfined profile cyclic reference and forward
>> * all new unconfined profiles requests to the default namespace
>> * This will result in all confined tasks that have a profile
>> * being removed inheriting the default->unconfined profile.
>> */
>> ns->unconfined = aa_get_profile(default_namespace->unconfined);
>> __aa_profile_list_release(&ns->base.profiles);
>> /* release original ns->unconfined ref */
>> aa_put_profile(unconfined);
>> /* release ns->base.list ref, from removal above */
>> aa_put_namespace(ns);
>
> aa_put_profile() and aa_put_namespace() may call write_lock() inside
> free_aa_profile(). Are you sure that these calls do not dead lock?
>
Yes, though I will give it another run through and reverify and add better
comments. The locking is such that the profile should be removed from
the list before free_aa_profile is called, and once in free_aa_profile
the lock is taken and released before any put_ is done.

>> }
>
>
>
>> ssize_t aa_interface_remove_profiles(char *name, size_t size)
> (...snipped...)
>> write_lock(&ns_list_lock);
>> if (name[0] == ':') {
>> char *ns_name;
>> name = aa_split_name_from_ns(name, &ns_name);
>> /* released below */
>> ns = aa_get_namespace(__aa_find_namespace(&ns_list, ns_name));
>
> aa_split_name_from_ns() may set ns_name to NULL but __aa_find_namespace() can't
> handle ns_name == NULL case. I think you should check ns_name != NULL.
>
yep

>
>
> Regarding policy_unpack.c
>
> Please use bool for functions that return 0 or 1.
>
>
>
>> static void audit_cb(struct audit_buffer *ab, void *va)
>> {
>> struct aa_audit_iface *sa = va;
>
> static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {
> struct aa_audit_iface *sa = container_of(va, struct aa_audit_iface,
> base);
>
>
>
>> static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
> (...snipped...)
>> for (i = 0; i < size; i++) {
>> char *tmp;
>> if (!unpack_dynstring(e, &tmp, NULL))
>> goto fail;
>> /*
>> * note: strings beginning with a : have an embedded
>> * \0 seperating the profile ns name from the profile
>> * name
>> */
>> profile->file.trans.table[i] = tmp;
>
> unpack_dynstring() returns string duplicated by kstrdup(). Thus, "tmp" can't
> have an embedded \0 seperating the profile ns name from the profile name
> even if tmp[0] == ':' is true, can it?
>
indeed, I should not have switched to kstrdup.

>> }
>
>
>
> Regarding resource.c
>
>> static void audit_cb(struct audit_buffer *ab, void *va)
>> {
>> struct aa_audit_resource *sa = va;
>
> static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {
> struct aa_audit_resource *sa = container_of(va, struct aa_audit_resource,
> base);
>
>
>
>> static int aa_audit_resource(struct aa_profile *profile,
>> struct aa_audit_resource *sa)
>> {
>> return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
>> audit_cb);
>
> return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa->base, audit_cb);
>
>> }
>
>
>
> Regarding sid.c
>
>> int aa_add_sid_profile(u32 sid, struct aa_profile *profile)
>
> This function is not used.
>
>
>
>> int aa_replace_sid_profile(u32 sid, struct aa_profile *profile)
>
> This function is not used.
>
>
>
>> struct aa_profile *aa_get_sid_profile(u32 sid)
>
> This function is not used.

right will fix

thanks

2009-11-23 10:11:18

by John Johansen

[permalink] [raw]
Subject: Re: [AppArmor #3 0/12] AppArmor security module

Tetsuo Handa wrote:
> Regarding file.c ipc.c lib.c lsm.c
>
>
>
> You can use container_of() inside callback functions to avoid "void *".
>
yeah that is cleaner, will do

>> int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
>> void (*cb) (struct audit_buffer *, void *))
>
> int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
> void (*cb) (struct audit_buffer *, struct aa_audit *))
>
>> static int aa_audit_base(int type, struct aa_profile *profile,
>> struct aa_audit *sa, struct audit_context *audit_cxt,
>> void (*cb) (struct audit_buffer *, void *))
>
> static int aa_audit_base(int type, struct aa_profile *profile,
> struct aa_audit *sa, struct audit_context *audit_cxt,
> void (*cb) (struct audit_buffer *, struct aa_audit *))
>
>> void file_audit_cb(struct audit_buffer *ab, void *va)
>> {
>> struct aa_audit_file *sa = va;
>
> void file_audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {
> struct aa_audit_file *sa = container_of(va, struct aa_audit_file, base);
>
>> int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa)
>> (...snipped...)
>> return aa_audit(type, profile, (struct aa_audit *)sa, file_audit_cb);
>
> return aa_audit(type, profile, &sa->base, file_audit_cb);
>
>> }
>
>> static void audit_cb(struct audit_buffer *ab, void *va)
>> {
>> struct aa_audit_ptrace *sa = va;
>
> static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {
> struct aa_audit_ptrace *sa = container_of(va, struct aa_audit_ptrace,
> base);
>
>> static int aa_audit_ptrace(struct aa_profile *profile,
>> struct aa_audit_ptrace *sa)
>> {
>> return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
>> audit_cb);
>
> return aa_audit(AUDIT_APPARMOR_AUTO, profile, &sa->base, audit_cb);
>
>
>
>> int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
>> struct path *new_dir, struct dentry *new_dentry)
> (.,..snipped...)
>> unsigned int state;
> (.,..snipped...)
>> sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, &cond,
>> &state);
>
> "state" remains uninitialized if profile->file.dfa == NULL.
> Are you sure profile->file.dfa != NULL ?
>
No this needs to be fixed.

>
>
>> char *aa_strchrnul(const char *s, int c)
>> {
>> for (; *s != (char)c && *s != '\0'; ++s)
>> ;
>> return (char *)s;
>> }
>
> Only fqname_subname() calls aa_strchrnul() and
> fqname_subname() returns NULL if aa_strchrnul() returns '\0'.
> You can use strchr() instead.
>
hrmm right thanks

>> static const char *fqname_subname(const char *name)
>> {
>> char *split;
>> /* check for namespace which begins with a : and ends with : or \0 */
>> name = strstrip((char *)name);
>> if (*name == ':') {
>> split = aa_strchrnul(name + 1, ':');
>> if (*split == '\0')
>> return NULL;
>
> split = strchr(name + 1, ':');
> if (!split)
> return NULL;
>
yep

>> name = strstrip(split + 1);
>> }
>> for (split = strstr(name, "//"); split; split = strstr(name, "//"))
>> name = split + 2;
>>
>> return name;
>> }
>
>
>
>> char *aa_split_name_from_ns(char *args, char **ns_name)
>> {
>> char *name = strstrip(args);
>>
>> *ns_name = NULL;
>> if (args[0] == ':') {
>> char *split = strstrip(strchr(&args[1], ':'));
>>
>> if (!split)
>> return NULL;
>
>
> strchr() returns NULL if not found, and strstrip(NULL) will do strlen(NULL).
> strstrip() never returns NULL. Did you mean
>
> char *split = strchr(&args[1], ':');
>
> if (!split)
> return NULL;
> split = strstrip(split);
>
> ?
yes, thanks

>
>> *split = 0;
>> *ns_name = &args[1];
>> name = strstrip(split + 1);
>> }
>> if (*name == 0)
>> name = NULL;
>>
>> return name;
>> }
>
>
>
>> static int apparmor_sysctl(struct ctl_table *table, int op)
> This hook will be removed.
>
>> char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen)
> This function will no longer be needed.
>
>> int aa_pathstr_perm(struct aa_profile *profile, const char *op,
>> const char *name, u16 request, struct path_cond *cond)
> This function will no longer be needed.
>
yep

>
>
>> static int apparmor_file_permission(struct file *file, int mask)
>> {
>> /*
>> * TODO: cache profiles that have revalidated?
>> */
>> struct aa_file_cxt *fcxt = file->f_security;
>> struct aa_profile *profile, *fprofile = fcxt->profile;
>> int error = 0;
>>
>> if (!fprofile || !file->f_path.mnt ||
>> !mediated_filesystem(file->f_path.dentry->d_inode))
>> return 0;
>>
>> profile = aa_current_profile();
>>
>> #ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
>> /*
>> * AppArmor <= 2.4 revalidates files at access time instead
>> * of at exec.
>> */
>> if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
>> error = aa_file_perm(profile, "file_perm", file, mask);
>> #endif
>>
>> return error;
>> }
>
> Why need to call this function if CONFIG_SECURITY_APPARMOR_COMPAT_24=n ?
> I think we can do
>
>> static struct security_operations apparmor_ops = {
> (...snipped...)
>
> #ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
>
>> .file_permission = apparmor_file_permission,
>
> #endif
>
yes we can currently. Though this will change in the future, but for now
we should got with the cleaner switch.

> (...snipped...)
>> }
>
>
>
>> int aa_alloc_default_namespace(void)
>
> This function could be declared with __init attribute.
>
yep, thanks

>
>
>> static int __init apparmor_init(void)
> (...snipped...)
>> error = set_init_cxt();
>> if (error) {
>> AA_ERROR("Failed to set context on init task\n");
>> goto alloc_out;
>
> This should be
>
> goto register_security_out;
>
> in order to call aa_free_default_namespace().
>
>> }

indeed

thanks again for taking the time to review
john