2010-02-19 09:36:41

by John Johansen

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

This is the foruth general posting of 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_
* The full conversion of 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
* The user space interface CONFIG_APPARMOR_COMPAT_24 has not been removed,
as the replacement interface isn't ready yet. It will become a separate
patch that distros can carry to provide backwards compatibility.


_Issues Addressed Since Last Time AppArmor was Posted_
* The majority of issues raised from the previous posting have been
addressed. Those that weren't are waiting on the completion of the
two major items addressed above.
* The dfa code was fully separated from the rest of the AppArmor code
generalizing it so that it could be used by other projects.
* mixed use of NULL of null and unconfined profiles to mean unconfined
has been removed. This lead to some significant cleanups that
makes the code smaller and easier to read.
* the task_context has been cleaned up and the context_group has been
removed. This lead to several cleanups in the code. The functionality
of the context_group will be reintroduced later with a newer more
generic implementation.
* Provide full basic implementation of hierarchial Profile namespaces.
The profile namespace code existed before but it was in a half finished
experimental state. It wasn't hierarchical and had other issues.
Move to root namespace instead of default namespace and get rid of the
ns_list_lock.

This necessitated an updating of the locking, which remains course
at the profile namespace level. The unused lock from the profile was
removed and a couple locking bugs were discovered in the process and
fixed. The auto removal of unused null learning profiles has been
removed until the profile lists have been converted over to RCU lists.
* CAP_MAC_ADMIN is now used to control all policy manipulations
* The upack interface had several minor tweeks and comments cleanups
The dfa permission checking was moved into it and made more rigourous.
* chmod and chown path mediation were reintroduced.
* d_namespace path was updated to make it more flexible providing better
control of how pathnames are generated.
* merged interface_add_profile and interface_replace_profile as they
were slight variation of each other
* Reworked domain code to not use error pointers
* removed incomplete set capability functionality. It provided similar
abilities as fscaps and pam_cap, which cover the majority of uses
it was intended for.
* get_procattr code cleaned up and generalized
* full implementation of the change_hat interface added allowing specifying
more than one potential targets reducing user space probing
* updated change_hat error codes, to match documentation
* update and rename policy_common struct to policy
* fixed a couple of oops in profile unpacking and verification
* update PROFILE_xxx macros to better reflect what they do
* Updated and expanded commenting on several functions


A Detailed list of all changes and patches are available from the AppArmor
git repository


The AppArmor project is has recently transitioned away from Novell forge.
Code and Documentation can be found at the following locations
* Documentation (early wip) - http://apparmor.wiki.kernel.org/
* User space tools - https://launchpad.net/apparmor
* Kernel module -
git://git.kernel.org/pub/scm/linux/kernel/git/jj/linux-apparmor.git

The location of the new mailing lists have not been finalized.


2010-02-19 09:36:54

by John Johansen

[permalink] [raw]
Subject: [PATCH 02/12] 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

From: John Johansen <[email protected]>

Add the core routine for AppArmor auditing.

Signed-off-by: John Johansen <[email protected]>
---
include/linux/audit.h | 10 ++-
security/apparmor/audit.c | 149 +++++++++++++++++++++++++++++++++++++
security/apparmor/include/audit.h | 54 +++++++++++++
3 files changed, 212 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..67a8113
--- /dev/null
+++ b/security/apparmor/audit.c
@@ -0,0 +1,149 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor auditing functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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
+ */
+
+/**
+ *
+ * NOTE: profile can be NULL if audit is a none profile based message
+ */
+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 *))
+{
+ struct audit_buffer *ab = NULL;
+ struct task_struct *task = sa->task ? sa->task : current;
+
+ if (profile && DO_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=");
+ audit_log_string(ab, aa_audit_type[type - AUDIT_APPARMOR_AUDIT]);
+ }
+
+ if (sa->operation) {
+ audit_log_format(ab, " operation=");
+ audit_log_string(ab, sa->operation);
+ }
+
+ if (sa->info) {
+ audit_log_format(ab, " info=");
+ audit_log_string(ab, sa->info);
+ if (sa->error)
+ audit_log_format(ab, " error=%d", sa->error);
+ }
+
+ audit_log_format(ab, " pid=%d", task->pid);
+
+ if (profile && !unconfined(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->base.hname);
+
+ if (profile->ns != root_ns) {
+ audit_log_format(ab, " namespace=");
+ audit_log_untrustedstring(ab, profile->ns->base.hname);
+ }
+ }
+
+ 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
+ *
+ * Returns: error on failure
+ */
+int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
+ void (*cb) (struct audit_buffer *, struct aa_audit *))
+{
+ struct audit_context *audit_cxt;
+ audit_cxt = aa_g_logsyscall ? current->audit_context : NULL;
+
+ if (type == AUDIT_APPARMOR_AUTO) {
+ if (likely(!sa->error)) {
+ if (AUDIT_MODE(profile) != AUDIT_ALL)
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else if (COMPLAIN_MODE(profile))
+ type = AUDIT_APPARMOR_ALLOWED;
+ else
+ type = AUDIT_APPARMOR_DENIED;
+ }
+ if (AUDIT_MODE(profile) == AUDIT_QUIET ||
+ (type == AUDIT_APPARMOR_DENIED &&
+ AUDIT_MODE(profile) == AUDIT_QUIET))
+ return sa->error;
+
+ return aa_audit_base(type, profile, sa, audit_cxt, cb);
+}
diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h
new file mode 100644
index 0000000..d86cfee
--- /dev/null
+++ b/security/apparmor/include/audit.h
@@ -0,0 +1,54 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor auditing function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 *, struct aa_audit *));
+
+#endif /* __AA_AUDIT_H */
--
1.6.6.1

2010-02-19 09:37:23

by John Johansen

[permalink] [raw]
Subject: [PATCH 07/12] AppArmor /proc/<pid>/attr/* and apparmorfs interfaces to userspace.

From: John Johansen <[email protected]>

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 | 61 ++++
security/apparmor/apparmorfs.c | 506 ++++++++++++++++++++++++++++++++
security/apparmor/include/apparmorfs.h | 28 ++
security/apparmor/include/procattr.h | 26 ++
security/apparmor/procattr.c | 124 ++++++++
5 files changed, 745 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..f64aae8
--- /dev/null
+++ b/security/apparmor/apparmorfs-24.c
@@ -0,0 +1,61 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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"
+
+
+/* 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..6f0b280
--- /dev/null
+++ b/security/apparmor/apparmorfs.c
@@ -0,0 +1,506 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/security/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 void *kvmalloc(size_t size)
+{
+ void *buffer = kmalloc(size, GFP_KERNEL);
+ if (!buffer)
+ buffer = vmalloc(size);
+ return buffer;
+}
+
+static void kvfree(void *buffer)
+{
+ if (is_vmalloc_addr(buffer))
+ vfree(buffer);
+ else
+ kfree(buffer);
+}
+
+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)
+{
+ char *data;
+
+ if (*pos != 0) {
+ /* only writes from pos 0, that is complete writes */
+ data = ERR_PTR(-ESPIPE);
+ goto out;
+ }
+
+ /*
+ * Don't allow profile load/replace/remove from profiles that don't
+ * have CAP_MAC_ADMIN
+ */
+ if (!capable(CAP_MAC_ADMIN)) {
+ struct aa_profile *profile = NULL;
+ struct aa_audit sa = {
+ .operation = operation,
+ .gfp_mask = GFP_KERNEL,
+ .error = -EACCES,
+ };
+ profile = aa_current_profile();
+ data = ERR_PTR(aa_audit(AUDIT_APPARMOR_DENIED, profile, &sa,
+ NULL));
+ goto out;
+ }
+ /* freed by caller to aa_simple_write_to_buffer */
+ data = kvmalloc(alloc_size);
+ if (data == NULL) {
+ data = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ if (copy_from_user(data, userbuf, copy_size)) {
+ kvfree(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_replace_profiles(data, size, 1);
+ kvfree(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, 0);
+ kvfree(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);
+ kvfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_profile_remove = {
+ .write = aa_profile_remove
+};
+
+
+/**
+ * __next_namespace - find the next namespace to list
+ * @root: root namespace to stop search at
+ * @ns: current ns position
+ *
+ * Find the next namespace and to list and handle all locking needed
+ * while switching current namespace.
+ *
+ * NOTE: will not unlock root->lock
+ */
+static struct aa_namespace *__next_namespace(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ struct aa_namespace *parent;
+
+ /* is next namespace a child */
+ if (!list_empty(&ns->sub_ns)) {
+ struct aa_namespace *next;
+ next = list_first_entry(&ns->sub_ns, typeof(*ns), base.list);
+ read_lock(&next->lock);
+ return next;
+ }
+
+ parent = ns->parent;
+ while (parent) {
+ read_unlock(&ns->lock);
+ list_for_each_entry_continue(ns, &parent->sub_ns, base.list) {
+ read_lock(&ns->lock);
+ return ns;
+ }
+ if (parent == root)
+ return NULL;
+ ns = parent;
+ parent = parent->parent;
+ }
+
+ return NULL;
+}
+
+/**
+ * __first_profile - find the first profile in a namespace
+ * @root: namespace that is root of profiles being displayed
+ * @ns: namespace to start in
+ */
+ static struct aa_profile *__first_profile(struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ for ( ; ns; ns = __next_namespace(root, ns)) {
+ if (!list_empty(&ns->base.profiles))
+ return list_first_entry(&ns->base.profiles,
+ struct aa_profile, base.list);
+ }
+ return NULL;
+}
+
+/**
+ * __next_profile - step to the next profile in a profile tree
+ * @profile: current profile in tree
+ *
+ * Perform a depth first taversal on the profile tree in a namespace
+ *
+ * Returns: next profile or NULL if done
+ * Requires: profile->ns.lock to be held
+ */
+static struct aa_profile *__next_profile(struct aa_profile *p)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = p->ns;
+
+ /* is next profile a child */
+ if (!list_empty(&p->base.profiles))
+ return list_first_entry(&p->base.profiles, typeof(*p),
+ base.list);
+
+ /* is next profile a sibling, parent sibling, gp, subling, .. */
+ parent = p->parent;
+ while (parent) {
+ list_for_each_entry_continue(p, &parent->base.profiles,
+ base.list)
+ return p;
+ p = parent;
+ parent = parent->parent;
+ }
+
+ /* is next another profile in the namespace */
+ list_for_each_entry_continue(p, &ns->base.profiles, base.list)
+ return p;
+
+ return NULL;
+}
+
+/**
+ * next_profile - step to the next profile in where ever it may be
+ * @root: root namespace
+ * @profile: current profile
+ *
+ * Returns: next profile or NULL if there isn't one
+ */
+static struct aa_profile *next_profile(struct aa_namespace *root,
+ struct aa_profile *profile)
+{
+ struct aa_profile *next = __next_profile(profile);
+ if (next)
+ return next;
+
+ /* finished all profiles in namespace move to next namespace */
+ return __first_profile(root, __next_namespace(root, profile->ns));
+}
+
+/**
+ * p_start - start a depth first traversal of profile tree
+ * @f: seq_file to fill
+ * @pos: current position
+ *
+ * acquires first ns->lock
+ */
+static void *p_start(struct seq_file *f, loff_t *pos)
+ __acquires(root->lock)
+{
+ struct aa_profile *profile = NULL;
+ struct aa_namespace *root = aa_current_profile()->ns;
+ loff_t l = *pos;
+ f->private = aa_get_namespace(root);
+
+
+ /* find the first profile */
+ read_lock(&root->lock);
+ profile = __first_profile(root, root);
+
+ /* skip to position */
+ for (; profile && l > 0; l--)
+ profile = next_profile(root, profile);
+
+ return profile;
+}
+
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private;
+ (*pos)++;
+
+ return next_profile(root, 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
+ * locking.
+ */
+static void p_stop(struct seq_file *f, void *p)
+ __releases(root->lock)
+{
+ struct aa_profile *profile = p;
+ struct aa_namespace *root = f->private, *ns;
+
+ if (profile) {
+ for (ns = profile->ns; ns && ns != root; ns = ns->parent)
+ read_unlock(&ns->lock);
+ }
+ read_unlock(&root->lock);
+ aa_put_namespace(root);
+}
+
+/**
+ * print_ns_name - print a namespace name back to @root
+ * @root: root namespace to stop at
+ * @ns: namespace to gen name for
+ *
+ * Returns: true if it printed a name
+ */
+static bool print_ns_name(struct seq_file *f, struct aa_namespace *root,
+ struct aa_namespace *ns)
+{
+ if (!ns || ns == root)
+ return 0;
+
+ if (ns->parent && print_ns_name(f, root, ns->parent))
+ seq_printf(f, "//");
+
+ seq_printf(f, "%s", ns->base.name);
+ return 1;
+}
+
+/* Returns: error on failure */
+static int seq_show_profile(struct seq_file *f, void *p)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+ struct aa_namespace *root = f->private;
+
+ if (profile->ns != root)
+ seq_printf(f, ":");
+ if (print_ns_name(f, root, profile->ns))
+ seq_printf(f, "://");
+ seq_printf(f, "%s (%s)\n", profile->base.hname,
+ COMPLAIN_MODE(profile) ? "complain" : "enforce");
+
+ return 0;
+}
+
+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);
+}
+
+static const struct file_operations aa_fs_profiles_fops = {
+ .open = aa_profiles_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_profiles_release,
+};
+
+
+/** Base file system setup **/
+
+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");
+ aafs_remove("profiles");
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ aafs_remove("matching");
+ aafs_remove("features");
+#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
+ *
+ * Returns: error on failure
+ */
+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("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("profiles", 0440, &aa_fs_profiles_fops);
+ if (error)
+ goto error;
+ 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..d071bf1
--- /dev/null
+++ b/security/apparmor/include/apparmorfs.h
@@ -0,0 +1,28 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor filesystem definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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_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..6c6f271
--- /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-2010 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
+#define AA_ONEXEC 1
+
+int aa_getprocattr(struct aa_profile *profile, char **string);
+int aa_setprocattr_changehat(char *args, size_t size, int test);
+int aa_setprocattr_changeprofile(char *fqname, int onexec, int test);
+int aa_setprocattr_permipc(char *fqname);
+
+#endif /* __AA_PROCATTR_H */
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
new file mode 100644
index 0000000..f599c84
--- /dev/null
+++ b/security/apparmor/procattr.c
@@ -0,0 +1,124 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /proc/<pid>/attr/ interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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"
+
+/**
+ * aa_getprocattr - Return the profile information for @profile
+ * @profile: the profile to print profile info about
+ * @string: the string that will contain the profile and namespace info
+ *
+ * Returns: length of @string on success else error on failure
+ *
+ * Requires: profile != NULL
+ *
+ * Creates a string containing the namespace_name://profile_name for
+ * @profile.
+ */
+int aa_getprocattr(struct aa_profile *profile, char **string)
+{
+ char *str;
+ int len = 0, mode_len, name_len, ns_len = 0;
+ const char *mode_str = profile_mode_names[profile->mode];
+ struct aa_namespace *ns = profile->ns;
+ char *s;
+
+ mode_len = strlen(mode_str) + 3; /* + 3 for _() */
+ name_len = strlen(profile->base.hname);
+ if (ns != root_ns)
+ 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;
+ }
+ if (profile->flags & PFLAG_UNCONFINED)
+ sprintf(s, "%s\n", profile->base.hname);
+ else
+ sprintf(s, "%s (%s)\n", profile->base.hname, mode_str);
+ *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, size_t size, int test)
+{
+ char *hat;
+ u64 token;
+ const char *hats[16]; /* current hard limit on # of names */
+ int count = 0;
+
+ 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;
+ }
+
+ if (hat) {
+ /* set up hat name vector, args guarenteed null terminated
+ * at args[size]
+ */
+ char *end = args + size;
+ for (count = 0; (hat < end) && count < 16; ++count) {
+ char *next = hat + strlen(hat) + 1;
+ hats[count] = hat;
+ hat = next;
+ }
+ }
+
+ AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
+ __func__, token, hat ? hat : NULL);
+
+ return aa_change_hat(hats, count, token, test);
+}
+
+int aa_setprocattr_changeprofile(char *fqname, int onexec, int test)
+{
+ char *name, *ns_name;
+
+ name = aa_split_fqname(fqname, &ns_name);
+ return aa_change_profile(ns_name, name, onexec, test);
+}
+
+int aa_setprocattr_permipc(char *fqname)
+{
+ /* TODO: add ipc permission querying */
+ return -ENOTSUPP;
+}
--
1.6.6.1

2010-02-19 09:37:09

by John Johansen

[permalink] [raw]
Subject: [PATCH 04/12] 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

From: John Johansen <[email protected]>

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 | 291 ++++++++++
security/apparmor/include/sid.h | 45 ++
security/apparmor/policy.c | 1079 ++++++++++++++++++++++++++++++++++++
security/apparmor/sid.c | 70 +++
4 files changed, 1485 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..d49f768
--- /dev/null
+++ b/security/apparmor/include/policy.h
@@ -0,0 +1,291 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 COMPLAIN_MODE(_profile) \
+ ((aa_g_profile_mode == APPARMOR_COMPLAIN) || ((_profile) && \
+ (_profile)->mode == APPARMOR_COMPLAIN))
+
+#define DO_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_OLD_NULL_TRANS = 0x100, /* use // as the null transition */
+
+ /* These flags must coorespond with PATH_flags */
+ PFLAG_MEDIATE_DELETED = 0x10000, /* mediate instead delegate deleted */
+};
+
+#define AA_NEW_SID 0
+
+struct aa_profile;
+
+/* struct aa_policy - common part of both namespaces and profiles
+ * @name: name of the object
+ * @hname - The hierarchical name
+ * @count: reference count of the obj
+ * @list: list policy object is on
+ * @profiles: head of the profiles list contained in the object
+ */
+struct aa_policy {
+ char *name;
+ char *hname;
+ struct kref count;
+ 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
+ * @base: common policy
+ * @parent: parent of namespace
+ * @lock: lock for modifying the object
+ * @acct: accounting for the namespace
+ * @unconfined: special unconfined profile for the namespace
+ * @sub_ns: list of namespaces under the current 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.
+ *
+ * Namespaces are hierarchical and only namespaces and profiles below the
+ * current namespace are visible.
+ *
+ * Namespace names must be unique and can not contain the characters :/\0
+ *
+ * FIXME TODO: add vserver support so a vserer (can it all be done in userspace)
+ */
+struct aa_namespace {
+ struct aa_policy base;
+ struct aa_namespace *parent;
+ rwlock_t lock;
+ struct aa_ns_acct acct;
+ struct aa_profile *unconfined;
+ struct list_head sub_ns;
+};
+
+/* struct aa_profile - basic confinement data
+ * @base - base componets of the profile (name, refcount, lists, lock ...)
+ * @parent: parent of profile
+ * @ns: namespace the profile is in
+ * @replacedby: is set profile that replaced this profile
+ * @xmatch: optional extended matching for unconfined executables names
+ * @xmatch_len: 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
+ * @path_flags: flags controlling path generation 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 base;
+ struct aa_profile *parent;
+
+ struct aa_namespace *ns;
+ union {
+ struct aa_profile *replacedby;
+ const char *rename;
+ };
+ struct aa_dfa *xmatch;
+ int xmatch_len;
+ u32 sid;
+ enum audit_mode audit;
+ enum profile_mode mode;
+ u32 flags;
+ u32 path_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 aa_namespace *root_ns;
+extern enum profile_mode aa_g_profile_mode;
+
+void aa_add_profile(struct aa_policy *common, struct aa_profile *profile);
+
+int aa_alloc_root_ns(void);
+void aa_free_root_ns(void);
+void aa_free_namespace_kref(struct kref *kref);
+
+struct aa_namespace *aa_find_namespace(struct aa_namespace *root,
+ const char *name);
+
+static inline struct aa_policy *aa_get_common(struct aa_policy *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, aa_free_namespace_kref);
+}
+
+struct aa_profile *aa_alloc_profile(const char *name);
+struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat);
+void aa_free_profile_kref(struct kref *kref);
+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_replace_profiles(void *udata, size_t size, bool add_only);
+ssize_t aa_interface_remove_profiles(char *name, size_t size);
+
+#define unconfined(X) ((X)->flags & PFLAG_UNCONFINED)
+
+/**
+ * aa_newest_version - 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_newest_version(struct aa_profile *profile)
+{
+ if (unlikely(profile && profile->replacedby))
+ for (; profile->replacedby; profile = profile->replacedby) ;
+
+ return profile;
+}
+
+/**
+ * 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, aa_free_profile_kref);
+}
+
+static inline int 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..2a3059b
--- /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-2010 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..4a740ff
--- /dev/null
+++ b/security/apparmor/policy.c
@@ -0,0 +1,1079 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy manipulation functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 a profile namespace which is a container of
+ * visible profiles. Each namespace contains a special "unconfined" profile,
+ * which doesn't enforce 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
+ * user-XXXX - user defined profiles
+ *
+ * a // in a profile or namespace name indicates a hierarcical 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
+ *
+ * A profile or namespace name that can contain one or more // seperators
+ * is refered to as an hname (hierarchical).
+ * eg. /bin/bash//bin/ls
+ *
+ * An fqname is a name that may contain both namespace and profile hnames.
+ * eg. :ns:/bin/bash//bin/ls
+ *
+ * NOTES:
+ * - locking of profile lists is currently fairly coarse. All profile
+ * lists within a namespace use the namespace lock.
+ * FIXME: move profile lists to using rcu_lists
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "include/apparmor.h"
+#include "include/capability.h"
+#include "include/context.h"
+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/match.h"
+#include "include/path.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+#include "include/resource.h"
+#include "include/sid.h"
+
+
+/* root profile namespace */
+struct aa_namespace *root_ns;
+
+const char *profile_mode_names[] = {
+ "enforce",
+ "complain",
+ "kill",
+};
+
+/**
+ * hname_tail - find the last component of an hname
+ * @name: hname to find the tail component of
+ *
+ * Returns: the tail name component of an hname
+ */
+static const char *hname_tail(const char *hname)
+{
+ char *split;
+ /* check for namespace which begins with a : and ends with : or \0 */
+ hname = strstrip((char *)hname);
+ for (split = strstr(hname, "//"); split; split = strstr(hname, "//"))
+ hname = split + 2;
+
+ return hname;
+}
+
+/**
+ * policy_init - initialize a policy structure
+ * @policy: policy to initialize
+ * @name: name of the policy, init will make a copy of it
+ */
+static bool policy_init(struct aa_policy *policy, const char *name)
+{
+ /* freed by policy_free */
+ policy->hname = kstrdup(name, GFP_KERNEL);
+ if (!policy->hname)
+ return 0;
+ /* base.name is a substring of fqname */
+ policy->name = (char *)hname_tail(policy->hname);
+
+ INIT_LIST_HEAD(&policy->list);
+ INIT_LIST_HEAD(&policy->profiles);
+ kref_init(&policy->count);
+
+ return 1;
+}
+
+/**
+ * policy_destroy - free the elements referenced by @policy
+ * @policy: policy that is to have its elements freed
+ */
+static void policy_destroy(struct aa_policy *policy)
+{
+ /* still contains profiles -- invalid */
+ if (!list_empty(&policy->profiles)) {
+ AA_ERROR("%s: internal error, "
+ "policy '%s' still contains profiles\n",
+ __func__, policy->name);
+ BUG();
+ }
+ if (!list_empty(&policy->list)) {
+ AA_ERROR("%s: internal error, policy '%s' still on list\n",
+ __func__, policy->name);
+ BUG();
+ }
+
+ /* don't free name as its a subset of hname */
+ kzfree(policy->hname);
+}
+
+/**
+ * __policy_find - find a policy by @name on a policy list
+ * @head: list to search
+ * @name: name to search for
+ *
+ * Requires: correct locks for the @head list be held
+ *
+ * Returns: policy that match @name or NULL if not found
+ */
+static struct aa_policy *__policy_find(struct list_head *head, const char *name)
+{
+ struct aa_policy *policy;
+
+ list_for_each_entry(policy, head, list) {
+ if (!strcmp(policy->name, name))
+ return policy;
+ }
+ return NULL;
+}
+
+/**
+ * __policy_strn_find - find a policy thats name matches @len chars of @str
+ * @head: list to search
+ * @str: string to search for
+ * @len: length of match required
+ *
+ * Requires: correct locks for the @head list be held
+ *
+ * Returns: policy that match @str or NULL if not found
+ *
+ * if @len == strlen(@strlen) then this is equiv to __policy_find
+ * other wise it allows searching for policy by a partial match of name
+ */
+static struct aa_policy *__policy_strn_find(struct list_head *head,
+ const char *str, int len)
+{
+ struct aa_policy *policy;
+
+ list_for_each_entry(policy, head, list) {
+ if (aa_strneq(policy->name, str, len))
+ return policy;
+ }
+
+ return NULL;
+}
+
+/*
+ * Routines for AppArmor namespaces
+ */
+
+/**
+ * aa_alloc_namespace - allocate, initialize and return a new namespace
+ * @name: a preallocated name
+ * Returns NULL on failure.
+ */
+static struct aa_namespace *aa_alloc_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 (!policy_init(&ns->base, name))
+ goto fail_ns;
+ INIT_LIST_HEAD(&ns->sub_ns);
+ rwlock_init(&ns->lock);
+
+ /*
+ * null profile is not added to the profile list,
+ * released by aa_free_namespace
+ */
+ ns->unconfined = aa_alloc_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 aa_free_namespace, however aa_remove_namespace breaks
+ * the cyclic references (ns->unconfined, and unconfined->ns) and
+ * replaces with refs to parent namespace unconfined
+ */
+ ns->unconfined->ns = aa_get_namespace(ns);
+
+ return ns;
+
+fail_unconfined:
+ kzfree(ns->base.name);
+fail_ns:
+ kzfree(ns);
+ return NULL;
+}
+
+/**
+ * aa_free_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,
+ */
+static void aa_free_namespace(struct aa_namespace *ns)
+{
+ if (!ns)
+ return;
+
+ policy_destroy(&ns->base);
+ aa_put_namespace(ns->parent);
+
+ if (ns->unconfined && ns->unconfined->ns == ns)
+ ns->unconfined->ns = NULL;
+
+ aa_put_profile(ns->unconfined);
+ kzfree(ns);
+}
+
+/**
+ * aa_free_namespace_kref - free aa_namespace by kref (see aa_put_namespace)
+ * @kr: kref callback for freeing of a namespace
+ */
+void aa_free_namespace_kref(struct kref *kref)
+{
+ aa_free_namespace(container_of(kref, struct aa_namespace, base.count));
+}
+
+/**
+ * __aa_find_namespace - find a namespace on a list by @name
+ * @name - name of namespace to look for
+ *
+ * Return: unrefcounted namespace
+ *
+ * Requires: ns lock be held
+ */
+static struct aa_namespace *__aa_find_namespace(struct list_head *head,
+ const char *name)
+{
+ return (struct aa_namespace *)__policy_find(head, name);
+}
+
+/**
+ * aa_find_namespace - look up a profile namespace on the namespace list
+ * @root: namespace to search in
+ * @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(struct aa_namespace *root,
+ const char *name)
+{
+ struct aa_namespace *ns = NULL;
+
+ read_lock(&root->lock);
+ ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
+ read_unlock(&root->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
+ */
+static struct aa_namespace *aa_prepare_namespace(const char *name)
+{
+ struct aa_namespace *ns, *root;
+
+ root = aa_current_profile()->ns;
+
+ write_lock(&root->lock);
+ if (name)
+ /* released by caller */
+ ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
+ else
+ /* released by caller */
+ ns = aa_get_namespace(root);
+ if (!ns) {
+ /* name && namespace not found */
+ struct aa_namespace *new_ns;
+ write_unlock(&root->lock);
+ new_ns = aa_alloc_namespace(name);
+ if (!new_ns)
+ return NULL;
+ write_lock(&root->lock);
+ /* test for race when new_ns was allocated */
+ ns = __aa_find_namespace(&root->sub_ns, name);
+ if (!ns) {
+ /* add parent ref */
+ new_ns->parent = aa_get_namespace(root);
+
+ list_add(&new_ns->base.list, &root->sub_ns);
+ /* add list ref */
+ ns = aa_get_namespace(new_ns);
+ } else {
+ /* raced so free the new one */
+ aa_free_namespace(new_ns);
+ /* get reference on namespace */
+ aa_get_namespace(ns);
+ }
+ }
+ write_unlock(&root->lock);
+
+ /* return ref */
+ return ns;
+}
+
+/**
+ * __aa_add_profile - add a profile to a list
+ * @list: list to add it to
+ * @profile: the profile to add
+ *
+ * refcount @profile, should be put by __aa_remove_profile
+ *
+ * Requires: namespace lock be held, or list not be shared
+ */
+static void __aa_add_profile(struct list_head *list,
+ struct aa_profile *profile)
+{
+ list_add(&profile->base.list, list);
+ /* 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
+ * replacements to the unconfined profile.
+ *
+ * put @profile list refcount
+ *
+ * Requires: namespace lock be held, or list not have been live
+ */
+static 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
+ * unconfined profile for old's namespace.
+ *
+ * refcount @new for list, put @old list refcount
+ *
+ * Requires: namespace list lock be held, or list not be shared
+ */
+static void __aa_replace_profile(struct aa_profile *old,
+ struct aa_profile *new)
+{
+ struct aa_policy *policy;
+ struct aa_profile *child, *tmp;
+
+ if (old->parent)
+ policy = &old->parent->base;
+ else
+ policy = &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(&policy->profiles, 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 aa_free_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
+ */
+static 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);
+ }
+}
+
+static void __aa_remove_namespace(struct aa_namespace *ns);
+
+/**
+ * __aa_ns_list_release - remove all profile namespaces on the list put refs
+ * @head: list of profile namespaces
+ *
+ * Requires: namespace lock be held
+ */
+static void __aa_ns_list_release(struct list_head *head)
+{
+ struct aa_namespace *ns, *tmp;
+ list_for_each_entry_safe(ns, tmp, head, base.list)
+ __aa_remove_namespace(ns);
+
+}
+
+/**
+ * aa_destroy_namespace - remove everything contained by @ns
+ * @ns: namespace to have it contents removed
+ */
+static void aa_destroy_namespace(struct aa_namespace *ns)
+{
+ if (!ns)
+ return;
+
+ write_lock(&ns->lock);
+ /* release all profiles in this namespace */
+ __aa_profile_list_release(&ns->base.profiles);
+
+ /* release all sub namespaces */
+ __aa_ns_list_release(&ns->sub_ns);
+
+ write_unlock(&ns->lock);
+}
+
+/**
+ * __aa_remove_namespace - remove a namespace and all its children
+ * @ns: namespace to be removed
+ *
+ * Requires: ns->parent->lock be held and ns removed from parent.
+ */
+static 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 parent namespace
+ * This will result in all confined tasks that have a profile
+ * being removed, inheriting the parent->unconfined profile.
+ */
+ if (ns->parent)
+ ns->unconfined = aa_get_profile(ns->parent->unconfined);
+
+ aa_destroy_namespace(ns);
+
+ /* release original ns->unconfined ref */
+ aa_put_profile(unconfined);
+ /* release ns->base.list ref, from removal above */
+ aa_put_namespace(ns);
+}
+
+/**
+ * aa_alloc_root_ns - allocate the root profile namespace
+ *
+ * Returns 0 on success else error
+ *
+ */
+int __init aa_alloc_root_ns(void)
+{
+ /* released by aa_free_root_ns - used as list ref*/
+ root_ns = aa_alloc_namespace("root");
+ if (!root_ns)
+ return -ENOMEM;
+
+ return 0;
+}
+
+ /**
+ * aa_free_root_ns - free the root profile namespace
+ */
+void aa_free_root_ns(void)
+ {
+ struct aa_namespace *ns = root_ns;
+ root_ns = NULL;
+
+ aa_destroy_namespace(ns);
+ aa_put_namespace(ns);
+}
+
+/**
+ * aa_alloc_profile - allocate, initialize and return a new profile
+ * @hname: name of the profile
+ *
+ * Returns NULL on failure, else refcounted profile
+ */
+struct aa_profile *aa_alloc_profile(const char *hname)
+{
+ struct aa_profile *profile;
+
+ /* freed by aa_free_profile - usually through aa_put_profile */
+ profile = kzalloc(sizeof(*profile), GFP_KERNEL);
+ if (!profile)
+ return NULL;
+
+ if (!policy_init(&profile->base, hname)) {
+ kzfree(profile);
+ return NULL;
+ }
+
+ /* 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_new_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->base.hname) + 2 + 7 + 8, GFP_KERNEL);
+ if (!name)
+ goto fail;
+ sprintf(name, "%s//null-%x", parent->base.hname, sid);
+
+ profile = aa_alloc_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 aa_free_profile */
+ profile->parent = aa_get_profile(parent);
+ profile->ns = aa_get_namespace(parent->ns);
+
+ write_lock(&profile->ns->lock);
+ __aa_add_profile(&parent->base.profiles, profile);
+ write_unlock(&profile->ns->lock);
+
+ return profile;
+
+fail:
+ aa_free_sid(sid);
+ return NULL;
+}
+
+/**
+ * aa_free_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, aa_free_profile() will
+ * be called from an rcu callback routine, so we must not sleep here.
+ */
+static void aa_free_profile(struct aa_profile *profile)
+{
+ AA_DEBUG("%s(%p)\n", __func__, profile);
+
+ if (!profile)
+ return;
+
+ if (!list_empty(&profile->base.list)) {
+ AA_ERROR("%s: internal error, "
+ "profile '%s' still on ns list\n",
+ __func__, profile->base.name);
+ BUG();
+ }
+
+ /* free children profiles */
+ policy_destroy(&profile->base);
+ aa_put_profile(profile->parent);
+
+ aa_put_namespace(profile->ns);
+
+ 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_put_dfa(profile->xmatch);
+
+ if (profile->replacedby)
+ aa_put_profile(profile->replacedby);
+
+ kzfree(profile);
+}
+
+/**
+ * aa_free_profile_kref - free aa_profile by kref (called by aa_put_profile)
+ * @kr: kref callback for freeing of a profile
+ */
+void aa_free_profile_kref(struct kref *kref)
+{
+ struct aa_profile *p = container_of(kref, struct aa_profile,
+ base.count);
+
+ aa_free_profile(p);
+}
+
+/* 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 *)__policy_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 *)__policy_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->lock);
+ profile = aa_get_profile(__aa_find_child(&parent->base.profiles, name));
+ read_unlock(&parent->ns->lock);
+
+ return profile;
+}
+
+/**
+ * __aa_find_parent - lookup the parent of a profile of name @hname
+ * @ns: namespace to lookup profile in
+ * @hname: hierarchical profile name to find parent of
+ *
+ * Lookups up the parent of a fully qualified profile name, the profile
+ * that matches hname does not need to exist, in general this
+ * is used to load a new profile.
+ *
+ * Requires: ns->lock be held
+ *
+ * Returns: unrefcounted policy or NULL if not found
+ */
+static struct aa_policy *__aa_find_parent(struct aa_namespace *ns,
+ const char *hname)
+{
+ struct aa_policy *policy;
+ struct aa_profile *profile = NULL;
+ char *split;
+
+ policy = &ns->base;
+
+ for (split = strstr(hname, "//"); split;) {
+ profile = __aa_strn_find_child(&policy->profiles, hname,
+ split - hname);
+ if (!profile)
+ return NULL;
+ policy = &profile->base;
+ hname = split + 2;
+ split = strstr(hname, "//");
+ }
+ if (!profile)
+ return &ns->base;
+ return &profile->base;
+}
+
+/**
+ * __aa_find_profile - lookup the profile matching @hname
+ * @base: base list to start looking up profile name from
+ * @hname: hierarchical profile name
+ *
+ * Requires: ns->lock be held
+ *
+ * Returns: unrefcounted profile pointer or NULL if not found
+ *
+ * Do a relative name lookup, recursing through profile tree.
+ */
+static struct aa_profile *__aa_find_profile(struct aa_policy *base,
+ const char *hname)
+{
+ struct aa_profile *profile = NULL;
+ char *split;
+
+ for (split = strstr(hname, "//"); split;) {
+ profile = __aa_strn_find_child(&base->profiles, hname,
+ split - hname);
+ if (!profile)
+ return NULL;
+
+ base = &profile->base;
+ hname = split + 2;
+ split = strstr(hname, "//");
+ }
+
+ profile = __aa_find_child(&base->profiles, hname);
+
+ return profile;
+}
+
+/**
+ * aa_find_profile_by_name - find a profile by its full or partial name
+ * @ns: the namespace to start from
+ * @hname: 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 *hname)
+{
+ struct aa_profile *profile;
+
+ read_lock(&ns->lock);
+ profile = aa_get_profile(__aa_find_profile(&ns->base, hname));
+ read_unlock(&ns->lock);
+ return profile;
+}
+
+/**
+ * replacement_allowed - test to see if replacement is allowed
+ */
+static bool replacement_allowed(struct aa_profile *profile,
+ struct aa_audit_iface *sa,
+ int add_only)
+{
+ if (profile) {
+ if (profile->flags & PFLAG_IMMUTABLE) {
+ sa->base.info = "cannot replace immutible profile";
+ sa->base.error = -EPERM;
+ return 0;
+ } else if (add_only) {
+ sa->base.info = "profile already exists";
+ sa->base.error = -EEXIST;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/**
+ * __add_new_profile - simple wrapper around __aa_add_profile
+ * @ns: namespace that profile is being added to
+ * @policy: the policy container to add the profile to
+ * @profile: profile to add
+ *
+ * add a profile to a list and do other required basic allocations
+ */
+static void __add_new_profile(struct aa_namespace *ns,
+ struct aa_policy *policy,
+ struct aa_profile *profile)
+{
+ if (policy != &ns->base)
+ /* released on profile replacement or aa_free_profile */
+ profile->parent = aa_get_profile((struct aa_profile *) policy);
+ __aa_add_profile(&policy->profiles, profile);
+ /* released on aa_free_profile */
+ profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ profile->ns = aa_get_namespace(ns);
+}
+
+/**
+ * aa_interface_replace_profiles - replace profile(s) on the profile list
+ * @udata: serialized data stream
+ * @size: size of the serialized data stream
+ * @add_only: true if only doing addition, no replacement allowed
+ *
+ * unpack and replace a profile on the profile list and uses of that profile
+ * by any aa_task_cxt. 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, bool add_only)
+{
+ struct aa_policy *policy;
+ struct aa_profile *old_profile = NULL, *new_profile = NULL;
+ struct aa_profile *rename_profile = NULL;
+ struct aa_namespace *ns;
+ ssize_t error;
+ struct aa_audit_iface sa = {
+ .base.operation = "profile_replace",
+ .base.gfp_mask = GFP_ATOMIC,
+ };
+
+ /* check if loading policy is locked out */
+ if (aa_g_lock_policy) {
+ sa.base.info = "policy locked";
+ sa.base.error = -EACCES;
+ goto fail;
+ }
+
+ /* released below */
+ new_profile = aa_unpack(udata, size, &sa);
+ if (IS_ERR(new_profile)) {
+ sa.base.error = PTR_ERR(new_profile);
+ goto fail;
+ }
+
+ /* 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->base.hname;
+
+ write_lock(&ns->lock);
+ /* no ref on policy only use inside lock */
+ policy = __aa_find_parent(ns, new_profile->base.hname);
+
+ if (!policy) {
+ sa.base.info = "parent does not exist";
+ sa.base.error = -ENOENT;
+ goto audit;
+ }
+
+ old_profile = __aa_find_child(&policy->profiles,
+ new_profile->base.name);
+ /* released below */
+ aa_get_profile(old_profile);
+
+ if (new_profile->rename) {
+ rename_profile = __aa_find_profile(&ns->base,
+ new_profile->rename);
+ /* released below */
+ aa_get_profile(rename_profile);
+
+ /* must be cleared as it is shared with replaced-by */
+ kzfree(new_profile->rename);
+ new_profile->rename = NULL;
+
+ if (!rename_profile) {
+ sa.base.info = "profile to rename does not exist";
+ sa.base.error = -ENOENT;
+ goto audit;
+ }
+ }
+
+ if (!replacement_allowed(old_profile, &sa, add_only))
+ goto audit;
+
+ if (!replacement_allowed(rename_profile, &sa, add_only))
+ goto audit;
+
+audit:
+ if (!old_profile && !rename_profile)
+ sa.base.operation = "profile_load";
+
+ error = aa_audit_iface(&sa);
+
+ if (!error) {
+ if (old_profile)
+ __aa_replace_profile(old_profile, new_profile);
+ if (rename_profile)
+ __aa_replace_profile(rename_profile, new_profile);
+ if (!(old_profile || rename_profile))
+ __add_new_profile(ns, policy, new_profile);
+ }
+ write_unlock(&ns->lock);
+
+out:
+ aa_put_namespace(ns);
+ aa_put_profile(rename_profile);
+ aa_put_profile(old_profile);
+ aa_put_profile(new_profile);
+ if (error)
+ return error;
+ return size;
+
+fail:
+ error = aa_audit_iface(&sa);
+ goto out;
+}
+
+/**
+ * aa_interface_remove_profiles - remove profile(s) from the system
+ * @fqname: name of the profile or namespace to remove
+ * @size: size of the name
+ *
+ * Remove a profile or sub namespace from the current namespace, so that
+ * they can not be found anymore and mark them as replaced by unconfined
+ *
+ * NOTE: removing confinement does not restore rlimits to preconfinemnet values
+ */
+ssize_t aa_interface_remove_profiles(char *fqname, size_t size)
+{
+ struct aa_namespace *root, *ns = NULL;
+ struct aa_profile *profile = NULL;
+ struct aa_audit_iface sa = {
+ .base.operation = "profile_remove",
+ .base.gfp_mask = GFP_ATOMIC,
+ };
+ const char *name = fqname;
+ int error;
+
+ /* check if loading policy is locked out */
+ if (aa_g_lock_policy) {
+ sa.base.info = "policy locked";
+ sa.base.error = -EACCES;
+ goto fail;
+ }
+
+ if (*fqname == 0) {
+ sa.base.info = "no profile specified";
+ sa.base.error = -ENOENT;
+ goto fail;
+ }
+
+ /* ref count held by cred */
+ root = aa_current_profile()->ns;
+
+ if (fqname[0] == ':') {
+ char *ns_name;
+ name = aa_split_fqname(fqname, &ns_name);
+ if (ns_name)
+ /* released below */
+ ns = aa_find_namespace(root, ns_name);
+ } else
+ /* released below */
+ ns = aa_get_namespace(root);
+
+ if (!ns) {
+ sa.base.info = "namespace does not exist";
+ sa.base.error = -ENOENT;
+ goto fail;
+ }
+
+ sa.name2 = ns->base.name;
+ write_lock(&ns->lock);
+ if (!name) {
+ /* remove namespace - can only happen if fqname[0] == ':' */
+ __aa_remove_namespace(ns);
+ } else {
+ /* remove profile */
+ profile = aa_get_profile(__aa_find_profile(&ns->base, name));
+ if (!profile) {
+ sa.name = name;
+ sa.base.error = -ENOENT;
+ sa.base.info = "profile does not exist";
+ goto fail_ns_lock;
+ }
+ sa.name = profile->base.hname;
+ __aa_profile_list_release(&profile->base.profiles);
+ __aa_replace_profile(profile, NULL);
+ }
+ write_unlock(&ns->lock);
+
+ /* don't fail removal if audit fails */
+ (void) aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(profile);
+ return size;
+
+fail_ns_lock:
+ write_unlock(&ns->lock);
+ aa_put_namespace(ns);
+
+fail:
+ error = aa_audit_iface(&sa);
+ return error;
+}
diff --git a/security/apparmor/sid.c b/security/apparmor/sid.c
new file mode 100644
index 0000000..ff55637
--- /dev/null
+++ b/security/apparmor/sid.c
@@ -0,0 +1,70 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor security identifier (sid) manipulation fns
+ *
+ * Copyright 2009-2010 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 */
+}
--
1.6.6.1

2010-02-19 09:37:29

by John Johansen

[permalink] [raw]
Subject: [PATCH 12/12] Kconfig and Makefiles to enable configuration and building of AppArmor.

From: John Johansen <[email protected]>

Signed-off-by: John Johansen <[email protected]>
---
security/Kconfig | 6 ++++
security/Makefile | 2 +
security/apparmor/.gitignore | 5 +++
security/apparmor/Kconfig | 62 ++++++++++++++++++++++++++++++++++++++++++
security/apparmor/Makefile | 25 +++++++++++++++++
5 files changed, 100 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 226b955..bd72ae6 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -140,6 +140,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

@@ -148,6 +149,7 @@ choice
default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
+ default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
default DEFAULT_SECURITY_DAC

help
@@ -163,6 +165,9 @@ choice
config DEFAULT_SECURITY_TOMOYO
bool "TOMOYO" if SECURITY_TOMOYO=y

+ config DEFAULT_SECURITY_APPARMOR
+ bool "AppArmor" if SECURITY_APPARMOR=y
+
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"

@@ -173,6 +178,7 @@ config DEFAULT_SECURITY
default "selinux" if DEFAULT_SECURITY_SELINUX
default "smack" if DEFAULT_SECURITY_SMACK
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
+ default "apparmor" if DEFAULT_SECURITY_APPARMOR
default "" if DEFAULT_SECURITY_DAC

endmenu
diff --git a/security/Makefile b/security/Makefile
index da20a19..8bb0fe9 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
@@ -19,6 +20,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_CGROUP_DEVICE) += device_cgroup.o

# Object integrity file lists
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.6.1

2010-02-19 09:37:54

by John Johansen

[permalink] [raw]
Subject: [PATCH 11/12] AppArmor hooks to interface with the LSM, module parameters and initialization.

From: John Johansen <[email protected]>

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/lsm.c | 1091 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1091 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..8d58905
--- /dev/null
+++ b/security/apparmor/lsm.c
@@ -0,0 +1,1091 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor LSM hooks.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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_cxt 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_cxt *cxt = aa_alloc_task_context(gfp);
+ if (cxt)
+ return -ENOMEM;
+
+ cred->security = cxt;
+ return 0;
+}
+
+/*
+ * prepare new aa_task_cxt 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_cxt *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_cxt *old_cxt = old->security;
+ struct aa_task_cxt *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)
+{
+ int rc;
+
+ rc = cap_ptrace_access_check(child, mode);
+ if (rc)
+ return rc;
+
+ 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);
+ profile = aa_cred_profile(cred);
+
+ *effective = cred->cap_effective;
+ *inheritable = cred->cap_inheritable;
+ *permitted = cred->cap_permitted;
+
+ if (!unconfined(profile))
+ *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);
+
+ profile = aa_cred_profile(cred);
+ if (!error && !unconfined(profile))
+ 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();
+
+ if (!unconfined(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 (!unconfined(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, AA_MAY_DELETE);
+}
+
+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, AA_MAY_DELETE);
+}
+
+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();
+ if (!unconfined(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();
+ if (!unconfined(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_path_chmod(struct dentry *dentry, struct vfsmount *mnt,
+ mode_t mode)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile();
+ if (!unconfined(profile)) {
+ struct path path = { mnt, dentry };
+ struct path_cond cond = { dentry->d_inode->i_uid,
+ dentry->d_inode->i_mode
+ };
+
+ error = aa_path_perm(profile, "chmod", &path, AA_MAY_CHMOD,
+ &cond);
+ }
+
+ return error;
+}
+
+static int apparmor_path_chown(struct path *path, uid_t uid, gid_t gid)
+{
+ struct aa_profile *profile;
+ int error = 0;
+
+ if (!mediated_filesystem(path->dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile();
+ if (!unconfined(profile)) {
+ struct path_cond cond = { path->dentry->d_inode->i_uid,
+ path->dentry->d_inode->i_mode
+ };
+ error = aa_path_perm(profile, "chown", path, AA_MAY_CHOWN,
+ &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;
+
+ profile = aa_cred_profile(cred);
+ if (!unconfined(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 (!unconfined(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();
+ if (!unconfined(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;
+
+ /* do DAC check */
+ 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_profile *profile;
+ /* released below */
+ const struct cred *cred = get_task_cred(task);
+ struct aa_task_cxt *cxt = cred->security;
+ profile = aa_cred_profile(cred);
+
+ if (strcmp(name, "current") == 0)
+ error = aa_getprocattr(aa_newest_version(cxt->profile),
+ value);
+ else if (strcmp(name, "prev") == 0 && cxt->previous)
+ error = aa_getprocattr(aa_newest_version(cxt->previous),
+ value);
+ else if (strcmp(name, "exec") == 0 && cxt->onexec)
+ error = aa_getprocattr(aa_newest_version(cxt->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;
+ size_t arg_size;
+ 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;
+
+ arg_size = size - (args - (char *) value);
+ if (strcmp(name, "current") == 0) {
+ if (strcmp(command, "changehat") == 0) {
+ error = aa_setprocattr_changehat(args, arg_size,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permhat") == 0) {
+ error = aa_setprocattr_changehat(args, arg_size,
+ AA_DO_TEST);
+ } else if (strcmp(command, "changeprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, !AA_ONEXEC,
+ 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), AA_ONEXEC,
+ !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();
+ int error = 0;
+
+ if (!unconfined(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 (!unconfined(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_chmod = apparmor_path_chmod,
+ .path_chown = apparmor_path_chown,
+ .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);
+
+/* Determines how paranoid loading of policy is and how much verification
+ * on the loaded policy is done.
+ */
+int aa_g_paranoid_load = 1;
+module_param_named(paranoid_load, aa_g_paranoid_load, aabool,
+ 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 (!capable(CAP_MAC_ADMIN))
+ 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 (!capable(CAP_MAC_ADMIN))
+ return -EPERM;
+ return param_get_bool(buffer, kp);
+}
+
+static int param_set_aabool(const char *val, struct kernel_param *kp)
+{
+ if (!capable(CAP_MAC_ADMIN))
+ return -EPERM;
+ return param_set_bool(val, kp);
+}
+
+static int param_get_aabool(char *buffer, struct kernel_param *kp)
+{
+ if (!capable(CAP_MAC_ADMIN))
+ return -EPERM;
+ return param_get_bool(buffer, kp);
+}
+
+static int param_set_aauint(const char *val, struct kernel_param *kp)
+{
+ if (!capable(CAP_MAC_ADMIN))
+ return -EPERM;
+ return param_set_uint(val, kp);
+}
+
+static int param_get_aauint(char *buffer, struct kernel_param *kp)
+{
+ if (!capable(CAP_MAC_ADMIN))
+ 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 (!capable(CAP_MAC_ADMIN))
+ 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 (!capable(CAP_MAC_ADMIN))
+ 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 (!capable(CAP_MAC_ADMIN))
+ 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 (!capable(CAP_MAC_ADMIN))
+ 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 (!capable(CAP_MAC_ADMIN))
+ 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_cxt *cxt;
+
+ cxt = aa_alloc_task_context(GFP_KERNEL);
+ if (!cxt)
+ return -ENOMEM;
+
+ cxt->profile = aa_get_profile(root_ns->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_root_ns();
+ 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 register_security_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_root_ns();
+
+alloc_out:
+ aa_destroy_aafs();
+
+ apparmor_enabled = 0;
+ return error;
+
+}
+
+security_initcall(apparmor_init);
+
+void apparmor_disable(void)
+{
+ /* FIXME: cleanup profiles references on files */
+ aa_free_root_ns();
+
+ aa_destroy_aafs();
+ apparmor_initialized = 0;
+
+ aa_info_message("AppArmor protection disabled");
+}
--
1.6.6.1

2010-02-19 09:37:19

by John Johansen

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

From: John Johansen <[email protected]>

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 | 453 ++++++++++++++++++++++++++++++++++++++
security/apparmor/include/file.h | 224 +++++++++++++++++++
2 files changed, 677 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..c4d04ef
--- /dev/null
+++ b/security/apparmor/file.c
@@ -0,0 +1,453 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor mediation of files
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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(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 | AA_MAY_DELETE | AA_MAY_CHMOD |
+ AA_MAY_CHOWN))
+ *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, u16 mask, int xindex,
+ int owner)
+{
+ char str[10];
+
+ if (owner) {
+ aa_audit_file_sub_mask(str, mask, xindex);
+ strcat(str, "::");
+ } else {
+ strcpy(str, "::");
+ aa_audit_file_sub_mask(str + 2, mask, xindex);
+ }
+ audit_log_string(ab, str);
+}
+
+static 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);
+ 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) {
+ audit_log_format(ab, " requested_mask=");
+ aa_audit_file_mask(ab, sa->request, AA_X_NONE,
+ fsuid == sa->cond->uid);
+ }
+ if (denied & AA_AUDIT_FILE_MASK) {
+ audit_log_format(ab, " denied_mask=");
+ aa_audit_file_mask(ab, 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
+ *
+ * Returns: error on failure
+ */
+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(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) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ AUDIT_MODE(profile) != AUDIT_ALL)
+ sa->request &= ~sa->perms.quiet;
+
+ if (!sa->request)
+ return COMPLAIN_MODE(profile) ? 0 : sa->base.error;
+ }
+ return aa_audit(type, profile, &sa->base, file_audit_cb);
+}
+
+/* TODO: convert from dfa + state to permission entry */
+static 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 | AA_MAY_CHMOD | AA_MAY_CHOWN */
+ if (perms.allowed & MAY_WRITE)
+ perms.allowed |= AA_MAY_CREATE | AA_MAY_CHMOD | AA_MAY_CHOWN |
+ AA_MAY_DELETE;
+ if (perms.audit & MAY_WRITE)
+ perms.audit |= AA_MAY_CREATE | AA_MAY_CHMOD | AA_MAY_CHOWN |
+ AA_MAY_DELETE;
+ if (perms.quiet & MAY_WRITE)
+ perms.quiet |= AA_MAY_CREATE | AA_MAY_CHMOD | AA_MAY_CHOWN |
+ AA_MAY_DELETE;
+
+ /* 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, profile->file.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,
+ };
+ int flags = profile->path_flags |
+ (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
+ /* buffer freed below - name is pointer inside buffer */
+ sa.base.error = aa_get_name(path, flags, &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, profile->file.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;
+}
+
+/* helper for aa_path_link - test target xindex == OR subset of link xindex */
+static inline bool xindex_is_subset(u16 link, u16 target)
+{
+ if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||
+ ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))
+ return 0;
+
+ return 1;
+}
+
+/**
+ * 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, profile->path_flags, &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, profile->path_flags, &buffer2,
+ &tname);
+ sa.name2 = tname;
+ if (sa.base.error)
+ goto audit;
+
+ sa.base.error = -EACCES;
+
+ /* aa_str_perms - handles the case of the dfa being NULL */
+ sa.perms = aa_str_perms(profile->file.dfa, profile->file.start, lname,
+ &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))
+ goto audit;
+
+ /* test to see if target can be paired with link */
+ state = aa_dfa_null_transition(profile->file.dfa, state,
+ profile->flags & PFLAG_OLD_NULL_TRANS);
+ perms = aa_str_perms(profile->file.dfa, state, tname, &cond, NULL);
+ if (!(perms.allowed & AA_MAY_LINK)) {
+ sa.base.info = "target restricted";
+ goto audit;
+ }
+
+ /* done if link subset test is not required */
+ if (!(perms.allowed & AA_LINK_SUBSET))
+ goto done_tests;
+
+ /* 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, profile->file.start, tname,
+ &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) {
+ goto audit;
+ } else if ((sa.perms.allowed & MAY_EXEC) &&
+ !xindex_is_subset(sa.perms.xindex, perms.xindex)) {
+ sa.perms.allowed &= ~MAY_EXEC;
+ sa.request |= MAY_EXEC;
+ sa.base.info = "link not subset of target";
+ goto audit;
+ }
+
+done_tests:
+ sa.base.error = 0;
+
+audit:
+ sa.base.error = aa_audit_file(profile, &sa);
+ kfree(buffer);
+ kfree(buffer2);
+
+ return sa.base.error;
+}
+
+static inline bool aa_is_deleted_file(struct dentry *dentry)
+{
+ if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
+ return 1;
+ return 0;
+}
+
+static 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, profile->file.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 flags = profile->path_flags | (S_ISDIR(mode) ? PATH_IS_DIR : 0);
+ int error = aa_get_name(&file->f_path, flags, &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..47ac4c7
--- /dev/null
+++ b/security/apparmor/include/file.h
@@ -0,0 +1,224 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor file mediation function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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_MAY_DELETE 0x0100
+#define AA_MAY_CHMOD 0x0200
+#define AA_MAY_CHOWN 0x0400
+
+#define AA_LINK_SUBSET 0x0800
+#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 | AA_MAY_DELETE | AA_MAY_CHMOD |\
+ AA_MAY_CHOWN)
+
+/*
+ * 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
+ * @xdelegate: used by exec to determine set of delegates allowed
+ * @dindex: delegate table index, 0 if no delegation allowed
+ *
+ * The @audit and @queit mask should be mutually exclusive.
+ */
+struct file_perms {
+ u16 allowed;
+ u16 audit;
+ u16 quiet;
+ u16 kill;
+ u16 xindex;
+ u16 xdelegate;
+ 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);
+
+/**
+ * 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 {
+ unsigned int start;
+ 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_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_put_dfa(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 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.6.1

2010-02-19 09:38:22

by John Johansen

[permalink] [raw]
Subject: [PATCH 10/12] AppArmor routines for controlling domain transitions

From: John Johansen <[email protected]>

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 | 745 ++++++++++++++++++++++++++++++++++++
security/apparmor/include/domain.h | 36 ++
2 files changed, 781 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..44a8079
--- /dev/null
+++ b/security/apparmor/domain.c
@@ -0,0 +1,745 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy attachment and domain transitions
+ *
+ * Copyright (C) 2002-2008 Novell/SUSE
+ * Copyright 2009-2010 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++)
+ kzfree(domain->table[i]);
+ kzfree(domain->table);
+ domain->table = NULL;
+}
+
+/*
+ * 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 = get_task_cred(tracer);
+ tracerp = aa_cred_profile(cred);
+ }
+ rcu_read_unlock();
+
+ /* not ptraced */
+ if (!tracer || unconfined(tracerp))
+ goto out;
+
+ error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
+
+out:
+ if (cred)
+ 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 (unconfined(profile)) {
+ perms.allowed = AA_MAY_CHANGE_PROFILE;
+ perms.xindex = perms.xdelegate = 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, profile->file.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, profile->file.start,
+ ns->base.name);
+ state = aa_dfa_null_transition(profile->file.dfa, state, 0);
+ return aa_str_perms(profile->file.dfa, state, name, &cond, rstate);
+}
+
+/* __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_find_attach - do attachment search for sys unconfined processes
+ * @ns: the current namespace
+ * @list: list to search
+ * @name: the executable name to match against
+ */
+static struct aa_profile *aa_find_attach(struct aa_namespace *ns,
+ struct list_head *list,
+ const char *name)
+{
+ struct aa_profile *profile;
+
+ read_lock(&ns->lock);
+ profile = aa_get_profile(__aa_attach_match(name, list));
+ read_unlock(&ns->lock);
+
+ return profile;
+}
+
+/**
+ * separate_fqname - separate the namespace and profile names
+ * @fqname: the fqname name to split
+ * @ns_name: the namespace name if it exists
+ *
+ * Returns: profile name if it is specified
+ *
+ * This is the xtable equivalent routine of aa_split_fqname. It finds the
+ * split in an xtable fqname which contains an embedded \0 instead of a :
+ * if a namespace is specified. This is done so the xtable is constant and
+ * isn't resplit on every lookup.
+ *
+ * Either the profile or namespace name may be optional but if the namespace
+ * is specified the profile name termination must be present. This results
+ * in the following possible encodings:
+ * profile_name\0
+ * :ns_name\0profile_name\0
+ * :ns_name\0\0
+ */
+static const char *separate_fqname(const char *fqname, const char **ns_name)
+{
+ const char *name;
+
+ if (fqname[0] == ':') {
+ *ns_name = fqname + 1; /* skip : */
+ name = *ns_name + strlen(*ns_name) + 1;
+ if (!*name)
+ name = NULL;
+ } else {
+ *ns_name = NULL;
+ name = fqname;
+ }
+
+ return name;
+}
+
+static const char *next_name(int xtype, const char *name)
+{
+ return NULL;
+}
+
+/**
+ * x_to_profile - get target profile for a given xindex
+ * @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 NULL if not found available
+ */
+static struct aa_profile *x_to_profile(struct aa_profile *profile,
+ const char *name, u16 xindex)
+{
+ struct aa_profile *new_profile = NULL;
+ struct aa_namespace *ns = profile->ns;
+ u16 xtype = xindex & AA_X_TYPE_MASK;
+ int index = xindex & AA_X_INDEX_MASK;
+
+ switch (xtype) {
+ case AA_X_NONE:
+ /* fail exec unless ix || ux fallback - handled by caller */
+ return NULL;
+ case AA_X_NAME:
+ if (xindex & AA_X_CHILD)
+ /* released by caller */
+ new_profile = aa_find_attach(ns,
+ &profile->base.profiles,
+ name);
+ else
+ /* released by caller */
+ new_profile = aa_find_attach(ns, &ns->base.profiles,
+ name);
+
+ goto out;
+ case AA_X_TABLE:
+ /* index is guarenteed to be in range */
+ 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;
+ xname = name = separate_fqname(name, &ns_name);
+ if (!xname)
+ /* no name so use profile name */
+ xname = profile->base.hname;
+ if (*ns_name == '@') {
+ /* TODO: variable support */
+ ;
+ }
+ /* released below */
+ new_ns = aa_find_namespace(ns, ns_name);
+ if (!new_ns)
+ continue;
+ } else if (*name == '@') {
+ /* TODO: variable support */
+ continue;
+ } else {
+ xname = name;
+ }
+
+ /* released by caller */
+ new_profile = aa_find_profile(new_ns ? new_ns : ns, xname);
+ aa_put_namespace(new_ns);
+ }
+
+out:
+ /* released by caller */
+ return new_profile;
+}
+
+/**
+ * apparmor_bprm_set_creds - set the new creds on the bprm struct
+ * @bprm: binprm for the exec
+ *
+ * Returns: error on failure
+ */
+int apparmor_bprm_set_creds(struct linux_binprm *bprm)
+{
+ struct aa_task_cxt *cxt;
+ struct aa_profile *profile, *new_profile = NULL;
+ struct aa_namespace *ns;
+ char *buffer = NULL;
+ unsigned int state;
+ 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_newest_version(cxt->profile);
+ /*
+ * get the namespace from the replacement profile as replacement
+ * can change the namespace
+ */
+ ns = profile->ns;
+ state = profile->file.start;
+
+ /* buffer freed below, name is pointer inside of buffer */
+ sa.base.error = aa_get_name(&bprm->file->f_path, profile->path_flags,
+ &buffer, (char **)&sa.name);
+ if (sa.base.error) {
+ if (profile->flags &
+ (PFLAG_IX_ON_NAME_ERROR | PFLAG_UNCONFINED))
+ sa.base.error = 0;
+ sa.base.info = "Exec failed name resolution";
+ sa.name = bprm->filename;
+ goto audit;
+ }
+
+ if (unconfined(profile)) {
+ /* unconfined task - attach profile if one matches */
+ new_profile = aa_find_attach(ns, &ns->base.profiles, sa.name);
+ if (!new_profile)
+ goto cleanup;
+ goto apply;
+ } else if (cxt->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->onexec->ns,
+ sa.name, &state);
+ state = aa_dfa_null_transition(profile->file.dfa, state, 0);
+ }
+ sa.perms = aa_str_perms(profile->file.dfa, state, sa.name, &cond, NULL);
+ if (cxt->onexec && sa.perms.allowed & AA_MAY_ONEXEC) {
+ /* transfer the onexec reference, this is allowed as the
+ * cred is being prepared, and isn't committed yet.
+ */
+ new_profile = cxt->onexec;
+ cxt->onexec = NULL;
+ sa.base.info = "change_profile onexec";
+ } else if (sa.perms.allowed & MAY_EXEC) {
+ new_profile = x_to_profile(profile, sa.name, sa.perms.xindex);
+ if (!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 = -ENOENT;
+ sa.base.info = "profile not found";
+ }
+ }
+ } else if (COMPLAIN_MODE(profile)) {
+ new_profile = aa_new_null_profile(profile, 0);
+ sa.base.error = -EACCES;
+ if (!new_profile) {
+ sa.base.error = -ENOMEM;
+ sa.base.info = "could not create null profile";
+ } else
+ sa.name2 = new_profile->base.hname;
+ 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)) {
+ AA_DEBUG("scubbing environment variables for %s profile=%s\n",
+ sa.name, new_profile->base.hname);
+ bprm->unsafe |= AA_SECURE_X_NEEDED;
+ }
+apply:
+ sa.name2 = new_profile->base.hname;
+ /* When switching namespace ensure its part of audit message */
+ if (new_profile->ns != ns)
+ sa.name3 = new_profile->ns->base.hname;
+
+ /* when transitioning profiles clear unsafe personality bits */
+ bprm->per_clear |= PER_CLEAR_ON_SETID;
+
+ aa_put_profile(cxt->profile);
+ /* transfer new profile reference will be released when cxt is freed */
+ cxt->profile = new_profile;
+
+x_clear:
+ aa_put_profile(cxt->previous);
+ aa_put_profile(cxt->onexec);
+ cxt->previous = NULL;
+ cxt->onexec = NULL;
+ cxt->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 = __aa_current_profile();
+ struct aa_task_cxt *new_cxt = bprm->cred->security;
+
+ /* bail out if unconfined or not changing profile */
+ if ((new_cxt->profile == profile) ||
+ (unconfined(new_cxt->profile)))
+ return;
+
+ current->pdeath_signal = 0;
+
+ /* reset soft limits and set hard limits for the new profile */
+ __aa_transition_rlimits(profile, new_cxt->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
+ * @hats: vector of hat names to try changing into (unused if @count == 0)
+ * @count: number of hat names in @hats
+ * @token: magic value to validate the hat change
+ * @permtest: true if this is just a permission test
+ *
+ * Change to the first profile specified in @hats that exists, and store
+ * the @hat_magic in the current task context. If the count == 0 and the
+ * @token matches that stored in the current task context, return to the
+ * top level profile.
+ *
+ * Returns %0 on success, error otherwise.
+ */
+int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
+{
+ const struct cred *cred;
+ struct aa_task_cxt *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;
+ int i;
+
+ /* released below */
+ cred = get_current_cred();
+ cxt = cred->security;
+ profile = aa_cred_profile(cred);
+ previous_profile = cxt->previous;
+
+ if (unconfined(profile)) {
+ sa.base.info = "unconfined";
+ sa.base.error = -EPERM;
+ goto audit;
+ }
+
+ if (count) {
+ /* attempting to change into a new hat or switch to a sibling */
+ struct aa_profile *root;
+ root = PROFILE_IS_HAT(profile) ? profile->parent : profile;
+ sa.name2 = profile->ns->base.hname;
+
+ /* find first matching hat */
+ for (i = 0; i < count && !hat; i++)
+ /* released below */
+ hat = aa_find_child(root, hats[i]);
+ if (!hat) {
+ if (!COMPLAIN_MODE(root) || permtest) {
+ sa.base.info = "hat not found";
+ if (list_empty(&root->base.profiles))
+ sa.base.error = -ECHILD;
+ else
+ sa.base.error = -ENOENT;
+ goto out;
+ }
+ /* freed below */
+ name = new_compound_name(root->base.hname, hats[0]);
+ sa.name = name;
+ /* released below */
+ hat = aa_new_null_profile(profile, 1);
+ if (!hat) {
+ sa.base.info = "failed null profile create";
+ sa.base.error = -ENOMEM;
+ goto audit;
+ }
+ } else {
+ sa.name = hat->base.hname;
+ 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)
+ /* kill task incase of brute force attacks */
+ 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) {
+ /* Return to saved profile. Kill task if restore fails
+ * to avoid brute force attacks
+ */
+ sa.name = previous_profile->base.hname;
+ 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);
+ put_cred(cred);
+
+ return sa.base.error;
+}
+
+/**
+ * aa_change_profile - perform a one-way profile transition
+ * @ns_name: name of the profile namespace to change to
+ * @hname: 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 *hname, int onexec,
+ bool permtest)
+{
+ const struct cred *cred;
+ struct aa_task_cxt *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 (!hname && !ns_name)
+ return -EINVAL;
+
+ if (onexec)
+ sa.base.operation = "change_onexec";
+ else
+ sa.base.operation = "change_profile";
+
+ cred = get_current_cred();
+ cxt = cred->security;
+ profile = aa_cred_profile(cred);
+
+ if (ns_name) {
+ /* released below */
+ ns = aa_find_namespace(profile->ns, ns_name);
+ if (!ns) {
+ /* we don't create new namespace in complain mode */
+ sa.name2 = ns_name;
+ sa.base.info = "namespace not found";
+ sa.base.error = -ENOENT;
+ goto audit;
+ }
+ sa.name2 = ns->base.hname;
+ } else {
+ /* released below */
+ ns = aa_get_namespace(profile->ns);
+ sa.name2 = ns->base.hname;
+ }
+
+ /* if the name was not specified, use the name of the current profile */
+ if (!hname) {
+ if (unconfined(profile))
+ hname = ns->unconfined->base.hname;
+ else
+ hname = profile->base.hname;
+ }
+ sa.name = hname;
+
+ sa.perms = change_profile_perms(profile, ns, hname, NULL);
+ if (!(sa.perms.allowed & AA_MAY_CHANGE_PROFILE)) {
+ sa.base.error = -EACCES;
+ goto audit;
+ }
+
+ /* released below */
+ target = aa_find_profile(ns, hname);
+ if (!target) {
+ sa.base.info = "profile not found";
+ sa.base.error = -ENOENT;
+ if (permtest || !COMPLAIN_MODE(profile))
+ goto audit;
+ /* release below */
+ target = aa_new_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);
+ put_cred(cred);
+
+ return sa.base.error;
+}
diff --git a/security/apparmor/include/domain.h b/security/apparmor/include/domain.h
new file mode 100644
index 0000000..b1ba488
--- /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-2010 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 *hats[], int count, u64 token, bool permtest);
+int aa_change_profile(const char *ns_name, const char *name, int onexec,
+ bool permtest);
+
+#endif /* __AA_DOMAIN_H */
--
1.6.6.1

2010-02-19 09:38:37

by John Johansen

[permalink] [raw]
Subject: [PATCH 09/12] AppArmor ipc, rlimit, network and capability routines

From: John Johansen <[email protected]>

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.

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 | 101 ++++++++++++++++++++++
security/apparmor/net.c | 145 ++++++++++++++++++++++++++++++++
security/apparmor/resource.c | 104 +++++++++++++++++++++++
8 files changed, 630 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..f45b891
--- /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-2010 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, struct aa_audit *va)
+{
+ struct aa_audit_caps *sa = container_of(va, struct aa_audit_caps, base);
+
+ 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((AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !cap_raised(profile->caps.audit, sa->cap)))
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else if (DO_KILL(profile) ||
+ cap_raised(profile->caps.kill, sa->cap)) {
+ type = AUDIT_APPARMOR_KILL;
+ } else if (cap_raised(profile->caps.quiet, sa->cap) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ 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 (COMPLAIN_MODE(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);
+}
+
+static 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 (COMPLAIN_MODE(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..8287e9d
--- /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-2010 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
+ * @allowed: capabilities mask
+ * @audit: caps that are to be audited
+ * @quiet: caps that should not be audited
+ * @kill: caps that when requested will result in the task being killed
+ * @extended: caps that are subject finer grained mediation
+ */
+struct aa_caps {
+ kernel_cap_t allowed;
+ kernel_cap_t audit;
+ kernel_cap_t quiet;
+ kernel_cap_t kill;
+ kernel_cap_t extended;
+};
+
+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..aeda0fb
--- /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-2010 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..7d6ddeb
--- /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-2010 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..e8fc079
--- /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-2010 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..e82c869
--- /dev/null
+++ b/security/apparmor/ipc.c
@@ -0,0 +1,101 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor ipc mediation
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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, struct aa_audit *va)
+{
+ struct aa_audit_ptrace *sa = container_of(va, struct aa_audit_ptrace,
+ base);
+ 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, &sa->base, audit_cb);
+}
+
+/* Returns error on failure */
+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 = get_task_cred(tracer);
+ int error = 0;
+ tracer_p = aa_cred_profile(cred);
+
+ if (!unconfined(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
+ */
+ struct aa_profile *tracee_p;
+ /* lcred released below */
+ struct cred *lcred = get_task_cred(tracee);
+ tracee_p = aa_cred_profile(lcred);
+
+ 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..b6123c4
--- /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-2010 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, struct aa_audit *va)
+{
+ struct aa_audit_net *sa = container_of(va, struct aa_audit_net, base);
+
+ if (sa->family || sa->type) {
+ if (address_family_names[sa->family]) {
+ audit_log_format(ab, " family=");
+ audit_log_string(ab, 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=");
+ audit_log_string(ab, sock_type_names[sa->type]);
+ } else {
+ audit_log_format(ab, " sock_type=\"unknown(%d)\"",
+ sa->type);
+ }
+ audit_log_format(ab, " protocol=%d", sa->protocol);
+ }
+
+}
+
+/* Returns: error on failure */
+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((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) &&
+ AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ AUDIT_MODE(profile) != AUDIT_ALL)
+ return COMPLAIN_MODE(profile) ? 0 : sa->base.error;
+ }
+
+ return aa_audit(type, profile, &sa->base, audit_cb);
+}
+
+/* Returns: error on failure */
+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);
+}
+
+/* Returns: error on failure */
+int aa_revalidate_sk(struct sock *sk, char *operation)
+{
+ struct aa_profile *profile;
+ 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;
+
+ profile = __aa_current_profile();
+ if (!unconfined(profile))
+ error = aa_net_perm(profile, operation,
+ sk->sk_family, sk->sk_type,
+ sk->sk_protocol);
+
+ return error;
+}
diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
new file mode 100644
index 0000000..0b6e350
--- /dev/null
+++ b/security/apparmor/resource.c
@@ -0,0 +1,104 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor resource mediation and attachment
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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, struct aa_audit *va)
+{
+ struct aa_audit_resource *sa = container_of(va,
+ struct aa_audit_resource,
+ base);
+
+ 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, &sa->base, 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.6.1

2010-02-19 09:36:52

by John Johansen

[permalink] [raw]
Subject: [PATCH 01/12] Miscellaneous functions and defines needed by AppArmor, including the base path resolution routines.

From: John Johansen <[email protected]>

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/apparmor.h | 82 +++++++++++++
security/apparmor/include/path.h | 31 +++++
security/apparmor/lib.c | 85 +++++++++++++
security/apparmor/path.c | 215 ++++++++++++++++++++++++++++++++++
4 files changed, 413 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..25c1647
--- /dev/null
+++ b/security/apparmor/include/apparmor.h
@@ -0,0 +1,82 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor basic global and lib definitions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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>
+
+#include "match.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 int aa_g_paranoid_load;
+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_fqname(char *args, char **ns_name);
+bool aa_strneq(const char *str, const char *sub, int len);
+void aa_info_message(const char *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
+ * @old: true if using // as the null transition
+ *
+ * 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.
+ */
+static inline unsigned int aa_dfa_null_transition(struct aa_dfa *dfa,
+ unsigned int start, bool old)
+{
+ if (unlikely(old))
+ return aa_dfa_match_len(dfa, start, "//", 2);
+ else
+ return aa_dfa_match_len(dfa, start, "\0", 1);
+}
+
+static inline bool 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..6933d64
--- /dev/null
+++ b/security/apparmor/include/path.h
@@ -0,0 +1,31 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor basic path manipulation function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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
+
+
+enum path_flags {
+ PATH_IS_DIR = 0x1, /* path is a directory */
+ PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */
+ PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */
+ PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */
+
+ PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
+};
+
+int aa_get_name(struct path *path, int flags, 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..896f8d2
--- /dev/null
+++ b/security/apparmor/lib.c
@@ -0,0 +1,85 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains basic common functions used in AppArmor
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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"
+
+
+/**
+ * aa_split_fqname - split a fqname into a profile and namespace name
+ * @fqname: a full qualified name in namespace profile format
+ * @ns_name: pointer to portion of the string containing the ns name
+ *
+ * Returns: profile name or NULL if one is not specified
+ *
+ * Split a namespace name from a profile name (see policy.c for naming
+ * description). If a portion of the name is missing it returns NULL for
+ * that portion.
+ *
+ * NOTE: may modifiy the @fqname string. The pointers returned point
+ * into the @fqname string.
+ */
+char *aa_split_fqname(char *fqname, char **ns_name)
+{
+ char *name = strstrip(fqname);
+
+ *ns_name = NULL;
+ if (fqname[0] == ':') {
+ char *split = strchr(&fqname[1], ':');
+ if (split) {
+ /* overwrite ':' with \0 */
+ *split = 0;
+ name = strstrip(split + 1);
+ } else
+ /* a ns name without a following profile is allowed */
+ name = NULL;
+ *ns_name = &fqname[1];
+ }
+ if (name && *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
+ */
+bool 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..a589d9d
--- /dev/null
+++ b/security/apparmor/path.c
@@ -0,0 +1,215 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor function for pathnames
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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, connected;
+ int error = 0;
+
+ read_lock(&current->fs->lock);
+ root = current->fs->root;
+ /* released below */
+ path_get(&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
+ *
+ * The stripping of (deleted) is a hack that could be removed
+ * with an updated __d_path
+ */
+ do {
+ if (flags & PATH_CHROOT_REL)
+ tmp = root;
+ else
+ tmp = ns_root;
+ deleted = d_unlinked(path->dentry);
+ res = __d_path(path, &tmp, buf, buflen);
+
+ } while (deleted != d_unlinked(path->dentry));
+ spin_unlock(&dcache_lock);
+
+ *name = res;
+ /* handle error conditions - and still allow a partial path to
+ * be returned.
+ */
+ if (IS_ERR(res)) {
+ error = PTR_ERR(res);
+ *name = buf;
+ goto out;
+ }
+ if (deleted) {
+ /* 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 string for
+ * normal mediation, or auditing. The (deleted) string is
+ * guarenteed to be added in this case, so just strip it.
+ */
+ buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */
+
+ if (path->dentry->d_inode && !(flags & PATH_MEDIATE_DELETED)) {
+ error = -ENOENT;
+ goto out;
+ }
+ }
+
+ if (flags & PATH_CHROOT_REL)
+ connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
+ else
+ connected = tmp.dentry == ns_root.dentry &&
+ tmp.mnt == ns_root.mnt;
+
+ if (!connected &&
+ !(flags & PATH_CONNECT_PATH) &&
+ !((flags & PATH_CHROOT_REL) && (flags & PATH_CHROOT_NSCONNECT) &&
+ (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;
+ }
+
+out:
+ path_put(&root);
+ path_put(&ns_root);
+
+ return error;
+}
+
+static int get_name_to_buffer(struct path *path, int flags, char *buffer,
+ int size, char **name)
+{
+ int adjust = (flags & PATH_IS_DIR) ? 1 : 0;
+ int error = d_namespace_path(path, buffer, size - adjust, name, flags);
+
+ if (!error && (flags & PATH_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
+ * @flags: flags controling path name generation
+ * @buffer: buffer that aa_get_name() allocated
+ * @name: the generated path name if there is an error
+ *
+ * Returns an error code if the there was a failure in obtaining the
+ * name.
+ *
+ * @name is a pointer 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 PATH_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 flags, 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, flags, buf, size, &str);
+ 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.6.1

2010-02-19 09:38:48

by John Johansen

[permalink] [raw]
Subject: [PATCH 06/12] 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.

From: John Johansen <[email protected]>

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 | 704 +++++++++++++++++++++++++++++
2 files changed, 734 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..5605a3e
--- /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-2010 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..299f50a
--- /dev/null
+++ b/security/apparmor/policy_unpack.c
@@ -0,0 +1,704 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions for unpacking policy loaded from
+ * userspace.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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, struct aa_audit *va)
+{
+ struct aa_audit_iface *sa = container_of(va, struct aa_audit_iface,
+ base);
+
+ if (sa->name) {
+ audit_log_format(ab, " name=");
+ audit_log_string(ab, sa->name);
+ }
+ if (sa->name2) {
+ audit_log_format(ab, " namespace=");
+ audit_log_string(ab, 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;
+ const struct cred *cred = get_current_cred();
+ int error;
+ profile = aa_cred_profile(cred);
+ error = aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa->base, audit_cb);
+ put_cred(cred);
+ return error;
+}
+
+static bool 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 bool 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 bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
+{
+ /*
+ * May need to reset pos if name or type doesn't match
+ */
+ 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 bool 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 bool 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 bool 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_str(struct aa_ext *e, const 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_strdup(struct aa_ext *e, char **string, const char *name)
+{
+ const char *tmp;
+ void *pos = e->pos;
+ int res = unpack_str(e, &tmp, name);
+ *string = NULL;
+
+ if (!res)
+ return 0;
+
+ *string = kmemdup(tmp, res, GFP_KERNEL);
+ if (!*string) {
+ e->pos = pos;
+ return 0;
+ }
+
+ return res;
+}
+
+static bool verify_accept(struct aa_dfa *dfa, int flags)
+{
+ int i;
+
+ /* verify accept permissions */
+ for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
+ int mode = ACCEPT_TABLE(dfa)[i];
+
+ if (mode & ~DFA_VALID_PERM_MASK)
+ return 0;
+
+ if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * unpack_dfa - unpack a file rule dfa
+ * @e: serialized data extent information
+ *
+ * returns dfa or ERR_PTR or NULL if no dfa
+ */
+static struct aa_dfa *unpack_dfa(struct aa_ext *e)
+{
+ char *blob = NULL;
+ size_t size;
+ struct aa_dfa *dfa = NULL;
+
+ size = unpack_blob(e, &blob, "aadfa");
+ if (size) {
+ /*
+ * 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;
+ int flags = TO_ACCEPT1_FLAG(YYTD_DATA32) |
+ TO_ACCEPT2_FLAG(YYTD_DATA32);
+
+
+ if (aa_g_paranoid_load)
+ flags |= DFA_FLAG_VERIFY_STATES;
+
+ dfa = aa_dfa_unpack(blob + pad, size - pad, flags);
+
+ if (!IS_ERR(dfa) && !verify_accept(dfa, flags))
+ goto fail;
+ }
+
+ return dfa;
+
+fail:
+ aa_put_dfa(dfa);
+ return ERR_PTR(-EPROTO);
+}
+
+static bool 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 *str;
+ int c, j, size = unpack_strdup(e, &str, NULL);
+ if (!size)
+ goto fail;
+ /*
+ * verify: transition names string
+ */
+ for (c = j = 0; j < size - 1; j++) {
+ if (!str[j])
+ c++;
+ }
+ /* names beginning with : require an embedded \0 */
+ if (*str == ':' && c != 1)
+ goto fail;
+ /* fail - all other cases with embedded \0 */
+ else if (c)
+ goto fail;
+ profile->file.trans.table[i] = str;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ aa_free_domain_entries(&profile->file.trans);
+ e->pos = pos;
+ return 0;
+}
+
+static bool 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;
+}
+
+/**
+ * unpack_profile - unpack a serialized profile
+ * @e: serialized data extent information
+ * @sa: audit struct for the operation
+ *
+ * NOTE: unpack profile sets audit struct if there is a failure
+ */
+static struct aa_profile *unpack_profile(struct aa_ext *e,
+ struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile = NULL;
+ const char *name = NULL;
+ size_t size = 0;
+ int i, error = -EPROTO;
+ kernel_cap_t tmpcap;
+ u32 tmp;
+ u64 tmp64;
+
+ /* check that we have the right struct being passed */
+ if (!unpack_nameX(e, AA_STRUCT, "profile"))
+ goto fail;
+ if (!unpack_str(e, &name, NULL))
+ goto fail;
+
+ profile = aa_alloc_profile(name);
+ if (!profile)
+ return ERR_PTR(-ENOMEM);
+
+ /* profile renaming is optional */
+ (void) unpack_str(e, &profile->rename, "rename");
+
+ /* xmatch is optional and may be NULL */
+ profile->xmatch = unpack_dfa(e);
+ if (IS_ERR(profile->xmatch)) {
+ error = PTR_ERR(profile->xmatch);
+ profile->xmatch = NULL;
+ goto fail;
+ }
+ /* xmatch_len is not optional if xmatch is set */
+ if (profile->xmatch) {
+ if (!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;
+
+ /* path_flags is optional */
+ unpack_u32(e, &profile->path_flags, "path_flags");
+ profile->path_flags |= profile->flags & PFLAG_MEDIATE_DELETED;
+
+ /* 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, &tmpcap.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, &(tmpcap.cap[1]), NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+
+ if (unpack_nameX(e, AA_STRUCT, "capsx")) {
+ /* optional extended caps mediation mask */
+ if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
+ goto fail;
+ }
+
+ if (!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 = unpack_dfa(e);
+ if (IS_ERR(profile->file.dfa)) {
+ error = PTR_ERR(profile->file.dfa);
+ profile->file.dfa = NULL;
+ goto fail;
+ }
+
+ if (!unpack_u32(e, &profile->file.start, "dfa_start"))
+ /* default start state */
+ profile->file.start = DFA_START;
+
+ if (!unpack_trans_table(e, profile))
+ goto fail;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ return profile;
+
+fail:
+ sa->name = name ? name : "unknown";
+ if (!sa->base.info)
+ sa->base.info = "failed to unpack profile";
+
+ aa_put_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_str(e, &sa->name2, "namespace"))
+ sa->name2 = NULL;
+
+ return 0;
+}
+
+/**
+ * verify_profile - Do post unpack analysis to verify profile consistency
+ * @profile: profile to verify
+ *
+ * Returns: 0 if passes verification else error
+ */
+static bool verify_xindex(int xindex, int table_size)
+{
+ int index, xtype;
+ xtype = xindex & AA_X_TYPE_MASK;
+ index = xindex & AA_X_INDEX_MASK;
+ if (xtype == AA_X_TABLE && index > table_size)
+ return 0;
+ return 1;
+}
+
+/* verify dfa xindexes are in range of transition tables */
+static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size)
+{
+ int i;
+ for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) {
+ if (!verify_xindex(dfa_user_xindex(dfa, i), table_size))
+ return 0;
+ if (!verify_xindex(dfa_other_xindex(dfa, i), table_size))
+ return 0;
+ }
+ return 1;
+}
+
+static int verify_profile(struct aa_profile *profile, struct aa_audit_iface *sa)
+{
+ if (aa_g_paranoid_load) {
+ if (profile->file.dfa &&
+ !verify_dfa_xindex(profile->file.dfa,
+ profile->file.trans.size)) {
+ sa->base.info = "Invalid named transition";
+ return -EPROTO;
+ }
+ }
+
+ 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 = unpack_profile(&e, sa);
+ if (IS_ERR(profile))
+ sa->pos = e.pos - e.start;
+
+ error = verify_profile(profile, sa);
+ if (error) {
+ aa_put_profile(profile);
+ profile = ERR_PTR(error);
+ }
+
+ /* return refcount */
+ return profile;
+}
--
1.6.6.1

2010-02-19 09:39:12

by John Johansen

[permalink] [raw]
Subject: [PATCH 05/12] A basic dfa matching engine based off the dfa engine in the Dragon Book. It uses simple row comb compression with a check field.

From: John Johansen <[email protected]>

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:
* 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 | 123 ++++++++++++++
security/apparmor/match.c | 330 +++++++++++++++++++++++++++++++++++++
2 files changed, 453 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..8631631
--- /dev/null
+++ b/security/apparmor/include/match.h
@@ -0,0 +1,123 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy dfa matching engine definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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
+#define YYTH_DEF_RECURSE 0x1 /* DEF Table is recursive */
+
+struct table_set_header {
+ u32 th_magic; /* YYTH_MAGIC */
+ u32 th_hsize;
+ u32 th_ssize;
+ u16 th_flags;
+ char th_version[];
+};
+
+/* The YYTD_ID are one less than flex table mappings. The flex id
+ * has 1 subtracted at table load time, this allows us to directly use the
+ * ID's as indexes.
+ */
+#define YYTD_ID_ACCEPT 0
+#define YYTD_ID_BASE 1
+#define YYTD_ID_CHK 2
+#define YYTD_ID_DEF 3
+#define YYTD_ID_EC 4
+#define YYTD_ID_META 5
+#define YYTD_ID_ACCEPT2 6
+#define YYTD_ID_NXT 7
+#define YYTD_ID_TSIZE 8
+
+#define YYTD_DATA8 1
+#define YYTD_DATA16 2
+#define YYTD_DATA32 4
+#define YYTD_DATA64 8
+
+/* Each ACCEPT2 table gets 6 dedicated flags, YYTD_DATAX define the
+ * first flags
+ */
+#define ACCEPT1_FLAGS(X) ((X) & 0x3f)
+#define ACCEPT2_FLAGS(X) ACCEPT1_FLAGS((X) >> YYTD_ID_ACCEPT2)
+#define TO_ACCEPT1_FLAG(X) ACCEPT1_FLAGS(X)
+#define TO_ACCEPT2_FLAG(X) (ACCEPT1_FLAGS(X) << YYTD_ID_ACCEPT2)
+#define DFA_FLAG_VERIFY_STATES 0x1000
+
+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]->td_data))
+#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE]->td_data))
+#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT]->td_data))
+#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK]->td_data))
+#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC]->td_data))
+#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT]->td_data))
+#define ACCEPT_TABLE2(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT2]->td_data))
+
+struct aa_dfa {
+ struct kref count;
+ u16 flags;
+ struct table_header *tables[YYTD_ID_TSIZE];
+};
+
+#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_dfa_unpack(void *blob, size_t size, int flags);
+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);
+void aa_dfa_free_kref(struct kref *kref);
+static inline void aa_put_dfa(struct aa_dfa *dfa)
+{
+ if (dfa)
+ kref_put(&dfa->count, aa_dfa_free_kref);
+}
+
+#endif /* __AA_MATCH_H */
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
new file mode 100644
index 0000000..6e6a816
--- /dev/null
+++ b/security/apparmor/match.c
@@ -0,0 +1,330 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor dfa based regular expression matching engine
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 <linux/err.h>
+#include <linux/kref.h>
+
+#include "include/match.h"
+
+static void free_table(struct table_header *table)
+{
+ if (is_vmalloc_addr(table))
+ vfree(table);
+ else
+ kzfree(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;
+
+ /* loaded td_id's start at 1, subtract 1 now to avoid doing
+ * it every time we use td_id as an index
+ */
+ th.td_id = be16_to_cpu(*(u16 *) (blob)) - 1;
+ 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 if (th.td_flags == YYTD_DATA32)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u32, be32_to_cpu);
+ else
+ goto fail;
+ }
+
+out:
+ return table;
+fail:
+ if (table)
+ free_table(table);
+ return NULL;
+}
+
+/**
+ * verify_dfa - verify that all the transitions and states in the dfa tables
+ * are in bounds.
+ * @dfa: dfa to test
+ * @flags: flags controlling what type of accept table are acceptable
+ *
+ * assumes dfa has gone through the verification done by unpacking
+ */
+static int verify_dfa(struct aa_dfa *dfa, int flags)
+{
+ size_t i, state_count, trans_count;
+ int error = -EPROTO;
+
+ /* check that required tables exist */
+ if (!(dfa->tables[YYTD_ID_DEF] &&
+ dfa->tables[YYTD_ID_BASE] &&
+ dfa->tables[YYTD_ID_NXT] && dfa->tables[YYTD_ID_CHK]))
+ goto out;
+
+ /* accept.size == default.size == base.size */
+ state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
+ if (ACCEPT1_FLAGS(flags)) {
+ if (!dfa->tables[YYTD_ID_ACCEPT])
+ goto out;
+ if (state_count != dfa->tables[YYTD_ID_ACCEPT]->td_lolen)
+ goto out;
+ }
+ if (ACCEPT2_FLAGS(flags)) {
+ if (!dfa->tables[YYTD_ID_ACCEPT2])
+ goto out;
+ if (state_count != dfa->tables[YYTD_ID_ACCEPT2]->td_lolen)
+ goto out;
+ }
+ if (state_count != dfa->tables[YYTD_ID_DEF]->td_lolen)
+ goto out;
+
+ /* next.size == chk.size */
+ trans_count = dfa->tables[YYTD_ID_NXT]->td_lolen;
+ if (trans_count != dfa->tables[YYTD_ID_CHK]->td_lolen)
+ goto out;
+
+ /* if equivalence classes then its table size must be 256 */
+ if (dfa->tables[YYTD_ID_EC] &&
+ dfa->tables[YYTD_ID_EC]->td_lolen != 256)
+ goto out;
+
+ if (flags & DFA_FLAG_VERIFY_STATES) {
+ for (i = 0; i < state_count; i++) {
+ if (DEFAULT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ /* TODO: do check that DEF state recursion terminates */
+ if (BASE_TABLE(dfa)[i] >= trans_count + 256)
+ goto out;
+ }
+
+ for (i = 0; i < trans_count; i++) {
+ if (NEXT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (CHECK_TABLE(dfa)[i] >= state_count)
+ goto out;
+ }
+ }
+
+ error = 0;
+out:
+ return error;
+}
+
+static void aa_dfa_free(struct aa_dfa *dfa)
+{
+ if (dfa) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
+ free_table(dfa->tables[i]);
+ dfa->tables[i] = NULL;
+ }
+ }
+ kfree(dfa);
+}
+
+/**
+ * aa_dfa_free_kref - free aa_dfa by kref (called by aa_put_dfa)
+ * @kr: kref callback for freeing of a dfa
+ */
+void aa_dfa_free_kref(struct kref *kref)
+{
+ struct aa_dfa *dfa = container_of(kref, struct aa_dfa, count);
+ aa_dfa_free(dfa);
+}
+
+/**
+ * aa_dfa_unpack - unpack the binary tables of a serialized dfa
+ * @blob: aligned serialized stream of data to unpack
+ * @size: size of data to unpack
+ * @flags: flags controlling what type of accept tables are acceptable
+ *
+ * Unpack a dfa that has been serialized. Dfa format and information in
+ * Documentation/AppArmor/dfa.txt
+ * Assumes the dfa @blob stream has been aligned on a 8 byte boundry
+ *
+ * Returns: an unpacked dfa ready for matching or ERR_PTR on failure
+ */
+struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
+{
+ int hsize;
+ int error = -ENOMEM;
+ struct aa_dfa *dfa = kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
+ if (!dfa)
+ goto fail;
+
+ kref_init(&dfa->count);
+
+ error = -EPROTO;
+
+ /* 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;
+
+ dfa->flags = ntohs(*(u16 *) (blob + 12));
+ blob += hsize;
+ size -= hsize;
+
+ while (size > 0) {
+ struct table_header *table;
+ table = unpack_table(blob, size);
+ if (!table)
+ goto fail;
+
+ switch (table->td_id) {
+ case YYTD_ID_ACCEPT:
+ if (!(table->td_flags & ACCEPT1_FLAGS(flags)))
+ goto fail;
+ break;
+ case YYTD_ID_ACCEPT2:
+ if (!(table->td_flags & ACCEPT2_FLAGS(flags)))
+ goto fail;
+ break;
+ case YYTD_ID_BASE:
+ if (table->td_flags != YYTD_DATA32)
+ goto fail;
+ break;
+ case YYTD_ID_DEF:
+ case YYTD_ID_NXT:
+ case YYTD_ID_CHK:
+ if (table->td_flags != YYTD_DATA16)
+ goto fail;
+ break;
+ case YYTD_ID_EC:
+ if (table->td_flags != YYTD_DATA8)
+ goto fail;
+ break;
+ default:
+ free_table(table);
+ goto fail;
+ }
+ dfa->tables[table->td_id] = table;
+ blob += table_size(table->td_lolen, table->td_flags);
+ size -= table_size(table->td_lolen, table->td_flags);
+ }
+
+ error = verify_dfa(dfa, flags);
+ if (error)
+ goto fail;
+
+ return dfa;
+
+fail:
+ aa_dfa_free(dfa);
+ return ERR_PTR(error);
+}
+
+/**
+ * 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]) {
+ /* Equivalence class table defined */
+ u8 *equiv = EQUIV_TABLE(dfa);
+ /* default is direct to next state */
+ for (; len; len--) {
+ pos = base[state] + equiv[(u8) *str++];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ } else {
+ /* default is direct to next state */
+ 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));
+}
--
1.6.6.1

2010-02-19 09:39:28

by John Johansen

[permalink] [raw]
Subject: [PATCH 03/12] AppArmor contexts attach profiles and state to tasks, files, etc. when a direct profile reference is not sufficient.

From: John Johansen <[email protected]>

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/context.c | 195 +++++++++++++++++++++++++++++++++++
security/apparmor/include/context.h | 142 +++++++++++++++++++++++++
2 files changed, 337 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..c2901af
--- /dev/null
+++ b/security/apparmor/context.c
@@ -0,0 +1,195 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions used to manipulate object security
+ * contexts.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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_cxt *aa_alloc_task_context(gfp_t flags)
+{
+ return kzalloc(sizeof(struct aa_task_cxt), flags);
+}
+
+void aa_free_task_context(struct aa_task_cxt *cxt)
+{
+ if (cxt) {
+ aa_put_profile(cxt->profile);
+ aa_put_profile(cxt->previous);
+ aa_put_profile(cxt->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_cxt *new, const struct aa_task_cxt *old)
+{
+ *new = *old;
+ aa_get_profile(new->profile);
+ aa_get_profile(new->previous);
+ aa_get_profile(new->onexec);
+}
+
+/**
+ * replace_cxt - replace a context profile
+ * @cxt: task context
+ * @profile: profile to replace cxt group
+ *
+ * Replace context grouping profile reference with @profile
+ */
+static void replace_group(struct aa_task_cxt *cxt, struct aa_profile *profile)
+{
+ if (cxt->profile == profile)
+ return;
+
+ BUG_ON(!profile);
+ if (unconfined(profile) || (cxt->profile->ns != profile->ns)) {
+ /* if switching to unconfined or a different profile namespace
+ * clear out context state
+ */
+ aa_put_profile(cxt->previous);
+ aa_put_profile(cxt->onexec);
+ cxt->previous = NULL;
+ cxt->onexec = NULL;
+ cxt->token = 0;
+ }
+ aa_put_profile(cxt->profile);
+ cxt->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_cxt *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ replace_group(cxt, 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_cxt *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ aa_put_profile(cxt->onexec);
+ cxt->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_cxt *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ if (!cxt->previous) {
+ cxt->previous = cxt->profile;
+ cxt->token = token;
+ } else if (cxt->token == token) {
+ aa_put_profile(cxt->profile);
+ } else {
+ /* previous_profile && cxt->token != token */
+ abort_creds(new);
+ return -EACCES;
+ }
+ cxt->profile = aa_get_profile(aa_newest_version(profile));
+ /* clear exec on switching context */
+ aa_put_profile(cxt->onexec);
+ cxt->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_cxt *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ if (cxt->token != token) {
+ abort_creds(new);
+ return -EACCES;
+ }
+ /* ignore restores when there is no saved profile */
+ if (!cxt->previous) {
+ abort_creds(new);
+ return 0;
+ }
+
+ aa_put_profile(cxt->profile);
+ cxt->profile = aa_newest_version(cxt->previous);
+ if (unlikely(cxt->profile != cxt->previous)) {
+ aa_get_profile(cxt->profile);
+ aa_put_profile(cxt->previous);
+ }
+ /* clear exec && prev information when restoring to previous context */
+ cxt->previous = NULL;
+ cxt->token = 0;
+ aa_put_profile(cxt->onexec);
+ cxt->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..8b53cf1
--- /dev/null
+++ b/security/apparmor/include/context.h
@@ -0,0 +1,142 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor contexts used to associate "labels" to objects.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009-2010 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 - primary label 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.
+ *
+ * TODO: make so a task can be confined by a stack of contexts
+ */
+struct aa_task_cxt {
+ struct aa_profile *profile;
+ struct aa_profile *onexec;
+ struct aa_profile *previous;
+ u64 token;
+};
+
+struct aa_task_cxt *aa_alloc_task_context(gfp_t flags);
+void aa_free_task_context(struct aa_task_cxt *cxt);
+void aa_dup_task_context(struct aa_task_cxt *new,
+ const struct aa_task_cxt *old);
+int aa_replace_current_profiles(struct aa_profile *sys);
+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);
+
+/**
+ * __aa_task_is_confined - determine if @task has any confinement
+ * @task: task to check confinement of
+ *
+ * If @task != current needs to be called in RCU safe critical section
+ */
+static inline bool __aa_task_is_confined(struct task_struct *task)
+{
+ struct aa_task_cxt *cxt = __task_cred(task)->security;
+
+ BUG_ON(!cxt);
+ if (unconfined(aa_newest_version(cxt->profile)))
+ return 0;
+
+ return 1;
+}
+
+/**
+ * aa_cred_profile - obtain cred's profiles
+ * @cred: cred to obtain profiles from
+ *
+ * Returns: system confining profile
+ *
+ * does NOT increment reference count
+ */
+static inline struct aa_profile *aa_cred_profile(const struct cred *cred)
+{
+ struct aa_task_cxt *cxt = cred->security;
+ BUG_ON(!cxt);
+ return aa_newest_version(cxt->profile);
+}
+
+/**
+ * __aa_current_profile - find the current tasks confining profile
+ *
+ * Returns: up to date confining profile or NULL if task is unconfined
+ *
+ * This fn will not update the tasks cred to the most up to date version
+ * of the profile so it is safe to call when inside of locks.
+ */
+static inline struct aa_profile *__aa_current_profile(void)
+{
+ return aa_cred_profile(current_cred());
+}
+
+/**
+ * aa_current_profile - find the current tasks confining profile and do updates
+ *
+ * Returns: up to date confinging profile or NULL if task is unconfined
+ *
+ * This fn will update the tasks cred structure if the profile has been
+ * replaced. Not safe to call inside locks
+ */
+static inline struct aa_profile *aa_current_profile(void)
+{
+ const struct aa_task_cxt *cxt = current_cred()->security;
+ struct aa_profile *profile;
+ BUG_ON(!cxt);
+
+ profile = aa_newest_version(cxt->profile);
+ /*
+ * Whether or not replacement succeeds, use newest profile so
+ * there is no need to update it after replacement.
+ */
+ if (unlikely((cxt->profile != profile)))
+ aa_replace_current_profiles(profile);
+
+ return profile;
+}
+
+#endif /* __AA_CONTEXT_H */
--
1.6.6.1

2010-02-19 11:03:23

by Al Viro

[permalink] [raw]
Subject: Re: [PATCH 01/12] Miscellaneous functions and defines needed by AppArmor, including the base path resolution routines.

On Fri, Feb 19, 2010 at 01:36:17AM -0800, [email protected] wrote:

> +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, connected;
> + int error = 0;
> +
> + read_lock(&current->fs->lock);
> + root = current->fs->root;
> + /* released below */
> + path_get(&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);

Junk. You might as well leave ns_root {NULL, NULL} instead of that crap.

> + 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
> + *
> + * The stripping of (deleted) is a hack that could be removed
> + * with an updated __d_path

Yes, it could. Where's the patch doing just that? Or discussion of
desired interface, at lease...

> + if (flags & PATH_CHROOT_REL)
> + connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
> + else
> + connected = tmp.dentry == ns_root.dentry &&
> + tmp.mnt == ns_root.mnt;
> +
> + if (!connected &&
> + !(flags & PATH_CONNECT_PATH) &&
> + !((flags & PATH_CHROOT_REL) && (flags & PATH_CHROOT_NSCONNECT) &&
> + (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;

Explanations, please.

2010-02-20 12:18:00

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 01/12] Miscellaneous functions and defines needed by AppArmor, including the base path resolution routines.

Al Viro wrote:
> On Fri, Feb 19, 2010 at 01:36:17AM -0800, [email protected] wrote:
>
>> +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, connected;
>> + int error = 0;
>> +
>> + read_lock(&current->fs->lock);
>> + root = current->fs->root;
>> + /* released below */
>> + path_get(&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);
>
> Junk. You might as well leave ns_root {NULL, NULL} instead of that crap.
>
Right, though the ns_root.mnt and ns_root.dentry are needed below to detect
disconnected paths.

<< snip >>

>
>> + if (flags & PATH_CHROOT_REL)
>> + connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
>> + else
>> + connected = tmp.dentry == ns_root.dentry &&
>> + tmp.mnt == ns_root.mnt;
>> +
>> + if (!connected &&
>> + !(flags & PATH_CONNECT_PATH) &&
>> + !((flags & PATH_CHROOT_REL) && (flags & PATH_CHROOT_NSCONNECT) &&
>> + (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;
>
> Explanations, please.

Right this needs to have a very good comment attached.

Basically AppArmor wants to know if the path is connected to the expected root
(either the chroot, or the namespace depending on how the profile is created).

When the file object isn't actually connected, and the profile is setup to use
file delegation instead of artificially connecting it to /, we stip the leading /
returned by __d_path disconnecting the path.

Objects disconnected from the root in this way can not be matched via regular path
matching rules but need to be delegated.

Previously we were doing this by patching __d_path to return disconnected paths
and then reattaching them in d_path, and getcwd. In many ways that was cleaner
as then we wouldn't need to grab to the ns_root to find if the path was connected
or not.

But for this submission I split any proposed changes to __d_path from AppArmor
so that they can be discussed separately.

2010-02-20 12:24:32

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 01/12] Miscellaneous functions and defines needed by AppArmor, including the base path resolution routines.

Al Viro wrote:
> On Fri, Feb 19, 2010 at 01:36:17AM -0800, [email protected] wrote:
>
>> + 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
>> + *
>> + * The stripping of (deleted) is a hack that could be removed
>> + * with an updated __d_path
>
> Yes, it could. Where's the patch doing just that? Or discussion of
> desired interface, at lease...
>
Glad you asked I was going to include it with a couple other patches to
__d_path. Basically trying to separate proposed changes to __d_path
from AppArmor, as most people who will be looking at the __d_path code
would rather not have the whole AppArmor patchset dropped on them.

The attached patch is just a first pass and a starting point for
discussion. I only modified TOMOYO to match current behavior of the
kernel and will leave it to Tetsuo to modify TOMOYO to make use
of the flag.

---

Make __d_path unambiguous for deleted files

__d_path currently appends the string " (deleted)" to deleted entries, but
this results in an ambiguous path. This is problematic for TOMOYO and
AppArmor as they use __d_path to retrieve path names for file objects.

This patch matches the appending the " (deleted)" string optional,
removing the need for AppArmor and TOMOYO to remove the string after
the fact.

Signed-off-by: John Johansen <[email protected]>

diff --git a/fs/dcache.c b/fs/dcache.c
index df49666..44c2afc 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1890,9 +1890,10 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
* @root: root vfsmnt/dentry (may be modified by this function)
* @buffer: buffer to return value in
* @buflen: buffer length
- *
+ * @mark_deleted: for deleted entries determine whether to append " (deleted)"
* Convert a dentry into an ASCII path name. If the entry has been deleted
- * the string " (deleted)" is appended. Note that this is ambiguous.
+ * and @mark_deleted is true then the string " (deleted)" is appended.
+ * Note that this is ambiguous.
*
* Returns a pointer into the buffer or an error code if the
* path was too long.
@@ -1903,7 +1904,7 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
* root is changed (without modifying refcounts).
*/
char *__d_path(const struct path *path, struct path *root,
- char *buffer, int buflen)
+ char *buffer, int buflen, bool mark_deleted)
{
struct dentry *dentry = path->dentry;
struct vfsmount *vfsmnt = path->mnt;
@@ -1912,7 +1913,7 @@ char *__d_path(const struct path *path, struct path *root,

spin_lock(&vfsmount_lock);
prepend(&end, &buflen, "\0", 1);
- if (d_unlinked(dentry) &&
+ if (d_unlinked(dentry) && mark_deleted &&
(prepend(&end, &buflen, " (deleted)", 10) != 0))
goto Elong;

@@ -2019,7 +2020,7 @@ char *d_path(const struct path *path, char *buf, int buflen)
read_unlock(&current->fs->lock);
spin_lock(&dcache_lock);
tmp = root;
- res = __d_path(path, &tmp, buf, buflen);
+ res = __d_path(path, &tmp, buf, buflen, DPATH_MARK_DELETED);
spin_unlock(&dcache_lock);
path_put(&root);
return res;
@@ -2124,7 +2125,7 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
struct path tmp = root;
char * cwd;

- cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE);
+ cwd = __d_path(&pwd, &tmp, page, PAGE_SIZE, DPATH_MARK_DELETED);
spin_unlock(&dcache_lock);

error = PTR_ERR(cwd);
diff --git a/fs/seq_file.c b/fs/seq_file.c
index eae7d9d..f046bd3 100644
--- a/fs/seq_file.c
+++ b/fs/seq_file.c
@@ -463,7 +463,7 @@ int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
char *p;

spin_lock(&dcache_lock);
- p = __d_path(path, root, buf, size);
+ p = __d_path(path, root, buf, size, DPATH_MARK_DELETED);
spin_unlock(&dcache_lock);
res = PTR_ERR(p);
if (!IS_ERR(p)) {
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 30b93b2..021f515 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -309,9 +309,12 @@ extern int d_validate(struct dentry *, struct dentry *);
/*
* helper function for dentry_operations.d_dname() members
*/
+#define DPATH_MARK_DELETED 1
+
extern char *dynamic_dname(struct dentry *, char *, int, const char *, ...);

-extern char *__d_path(const struct path *path, struct path *root, char *, int);
+extern char *__d_path(const struct path *path, struct path *root, char *, int,
+ bool);
extern char *d_path(const struct path *, char *, int);
extern char *dentry_path(struct dentry *, char *, int);

diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c
index 18369d4..0f4d4e2 100644
--- a/security/tomoyo/realpath.c
+++ b/security/tomoyo/realpath.c
@@ -106,7 +106,8 @@ int tomoyo_realpath_from_path2(struct path *path, char *newname,
spin_unlock(&vfsmount_lock);
spin_lock(&dcache_lock);
tmp = ns_root;
- sp = __d_path(path, &tmp, newname, newname_len);
+ sp = __d_path(path, &tmp, newname, newname_len,
+ DPATH_MARK_DELETED);
spin_unlock(&dcache_lock);
path_put(&root);
path_put(&ns_root);

2010-02-20 17:25:17

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 01/12] Miscellaneous functions and defines needed by AppArmor, including the base path resolution routines.

John Johansen wrote:
> Al Viro wrote:
>> On Fri, Feb 19, 2010 at 01:36:17AM -0800, [email protected] wrote:
>>
>>> +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, connected;
>>> + int error = 0;
>>> +
>>> + read_lock(&current->fs->lock);
>>> + root = current->fs->root;
>>> + /* released below */
>>> + path_get(&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);
>> Junk. You might as well leave ns_root {NULL, NULL} instead of that crap.
>>
> Right, though the ns_root.mnt and ns_root.dentry are needed below to detect
> disconnected paths.
>
Well no, actually I don't need it. After looking at this again,
If struct path tmp is unmodified we hit our expected root.

2010-02-20 19:11:05

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 01/12] Miscellaneous functions and defines needed by AppArmor, including the base path resolution routines.

John Johansen wrote:
> John Johansen wrote:
>> Al Viro wrote:
>>> On Fri, Feb 19, 2010 at 01:36:17AM -0800, [email protected] wrote:
>>>
>>>> +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, connected;
>>>> + int error = 0;
>>>> +
>>>> + read_lock(&current->fs->lock);
>>>> + root = current->fs->root;
>>>> + /* released below */
>>>> + path_get(&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);
>>> Junk. You might as well leave ns_root {NULL, NULL} instead of that crap.
>>>
>> Right, though the ns_root.mnt and ns_root.dentry are needed below to detect
>> disconnected paths.
>>
> Well no, actually I don't need it. After looking at this again,
> If struct path tmp is unmodified we hit our expected root.

Sigh so much for "brillant" ideas after a few hours of sleep. If tmp is modified
we only know that we made it to global_root: We still need to distinguish lazy
unmount, ideally we would do this in __d_path instead of here. I'll resurrect our
old patches doing just that and post them out for comment.

2010-02-22 22:14:16

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 11/12] AppArmor hooks to interface with the LSM, module parameters and initialization.

Quoting [email protected] ([email protected]):
> From: John Johansen <[email protected]>
>
> Signed-off-by: John Johansen <[email protected]>
> ---
> security/apparmor/lsm.c | 1091 +++++++++++++++++++++++++++++++++++++++++++++++
> 1 files changed, 1091 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..8d58905
> --- /dev/null
> +++ b/security/apparmor/lsm.c
> @@ -0,0 +1,1091 @@
> +/*
> + * AppArmor security module
> + *
> + * This file contains AppArmor LSM hooks.
> + *
> + * Copyright (C) 1998-2008 Novell/SUSE
> + * Copyright 2009-2010 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_cxt 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_cxt *cxt = aa_alloc_task_context(gfp);
> + if (cxt)
> + return -ENOMEM;

if (!cxt)? :)

> +
> + cred->security = cxt;
> + return 0;
> +}
> +
> +/*
> + * prepare new aa_task_cxt 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_cxt *cxt = aa_alloc_task_context(gfp);
> + if (!cxt)
> + return -ENOMEM;
> +
> + aa_dup_task_context(cxt, old->security);
> + new->security = cxt;
> + return 0;
> +}

Don't see any other problems on my first readthrough of this one, but
I'm trying walking backward through the set so will likely have to
come back to it...

thanks,
-serge

2010-02-22 22:17:00

by Serge E. Hallyn

[permalink] [raw]
Subject: Re: [PATCH 12/12] Kconfig and Makefiles to enable configuration and building of AppArmor.

Quoting [email protected] ([email protected]):
> From: John Johansen <[email protected]>
>
> Signed-off-by: John Johansen <[email protected]>
> ---
> security/Kconfig | 6 ++++
> security/Makefile | 2 +
> security/apparmor/.gitignore | 5 +++
> security/apparmor/Kconfig | 62 ++++++++++++++++++++++++++++++++++++++++++
> security/apparmor/Makefile | 25 +++++++++++++++++
> 5 files changed, 100 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 226b955..bd72ae6 100644
> --- a/security/Kconfig
> +++ b/security/Kconfig
> @@ -140,6 +140,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
>
> @@ -148,6 +149,7 @@ choice
> default DEFAULT_SECURITY_SELINUX if SECURITY_SELINUX
> default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
> default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
> + default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
> default DEFAULT_SECURITY_DAC
>
> help
> @@ -163,6 +165,9 @@ choice
> config DEFAULT_SECURITY_TOMOYO
> bool "TOMOYO" if SECURITY_TOMOYO=y
>
> + config DEFAULT_SECURITY_APPARMOR
> + bool "AppArmor" if SECURITY_APPARMOR=y
> +
> config DEFAULT_SECURITY_DAC
> bool "Unix Discretionary Access Controls"
>
> @@ -173,6 +178,7 @@ config DEFAULT_SECURITY
> default "selinux" if DEFAULT_SECURITY_SELINUX
> default "smack" if DEFAULT_SECURITY_SMACK
> default "tomoyo" if DEFAULT_SECURITY_TOMOYO
> + default "apparmor" if DEFAULT_SECURITY_APPARMOR
> default "" if DEFAULT_SECURITY_DAC
>
> endmenu
> diff --git a/security/Makefile b/security/Makefile
> index da20a19..8bb0fe9 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
> @@ -19,6 +20,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_CGROUP_DEVICE) += device_cgroup.o
>
> # Object integrity file lists
> 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.

Is there a compelling reason to have SECURITY_APPARMOR_NETWORK? Does
it impact performance? Is there older userspace that will just break?

-serge

2010-02-23 01:59:11

by Tetsuo Handa

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

Starting from apparmorfs.c and jumping randomly...





346 static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)(...snipped...)
369 /*
370 * verify: transition names string
371 */
372 for (c = j = 0; j < size - 1; j++) {
373 if (!str[j])
374 c++;
375 }
376 /* names beginning with : require an embedded \0 */
377 if (*str == ':' && c != 1)
378 goto fail;
379 /* fail - all other cases with embedded \0 */
380 else if (c)
381 goto fail;
382 profile->file.trans.table[i] = str;
You need to "profile->file.trans.table[i] = str;" before "goto fail;"
in order to let "aa_free_domain_entries()" to kzfree().





333 static struct aa_namespace *aa_prepare_namespace(const char *name)
334 {
335 struct aa_namespace *ns, *root;
336
337 root = aa_current_profile()->ns;
aa_current_profile() returns NULL if current_cred()->security->profile == NULL.

338
339 write_lock(&root->lock);
340 if (name)
341 /* released by caller */
342 ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
343 else
344 /* released by caller */
345 ns = aa_get_namespace(root);
The "if (!ns) { ... } " block is for only name != NULL case because
aa_alloc_namespace(NULL) returns NULL. Thus, you might want to do like

else {
/* released by caller */
ns = aa_get_namespace(root);
goto out_unlock;
}

369 }
out_unlock:
370 write_unlock(&root->lock);
371
372 /* return ref */
373 return ns;





889 ssize_t aa_interface_replace_profiles(void *udata, size_t size, bool add_only)
(...snipped...)
946 /* must be cleared as it is shared with replaced-by */
947 kzfree(new_profile->rename);
948 new_profile->rename = NULL;
Is it OK to clear now because replacement may not be taken place due to
aa_audit_iface() returning -ENOMEM?





108 static const char *hname_tail(const char *hname)
109 {
110 char *split;
111 /* check for namespace which begins with a : and ends with : or \0 */
112 hname = strstrip((char *)hname);
Oops. strstrip() has gone in 2.6.33 .





28 static void *kvmalloc(size_t size)
29 {
I think you should return NULL for size == 0.
kmalloc(0, GFP_KERNEL) returns ZERO_SIZE_PTR, which results in
copy_from_user(ZERO_SIZE_PTR, userbuf, (size_t) -1)
at aa_simple_write_to_buffer() if aa_profile_remove() got ((size_t) -1)
for "size" parameter. This will cause problem if access_ok() check inside
copy_from_user() is "return 1;".
30 void *buffer = kmalloc(size, GFP_KERNEL);
31 if (!buffer)
32 buffer = vmalloc(size);
33 return buffer;
34 }





1003 ssize_t aa_interface_remove_profiles(char *fqname, size_t size)
(...snipped...)
1027 /* ref count held by cred */
1028 root = aa_current_profile()->ns;
aa_current_profile() may return NULL.





278 static void *p_start(struct seq_file *f, loff_t *pos)
279 __acquires(root->lock)
280 {
281 struct aa_profile *profile = NULL;
282 struct aa_namespace *root = aa_current_profile()->ns;
aa_current_profile() may return NULL.

2010-02-23 07:45:11

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 12/12] Kconfig and Makefiles to enable configuration and building of AppArmor.

Serge E. Hallyn wrote:
> Quoting [email protected] ([email protected]):
>> From: John Johansen <[email protected]>
>>

<< snip >>

>> +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.
>
> Is there a compelling reason to have SECURITY_APPARMOR_NETWORK? Does
> it impact performance? Is there older userspace that will just break?
>
No, not really anymore. There used to be a case where I was building
with network hooks off and this has just been carried forward.

So it can go along with config APPARMOR_24_COMPAT, and I have even
been considering pulling the runtime disable as well as I don't
think there is a case for that either.

2010-02-23 07:58:17

by John Johansen

[permalink] [raw]
Subject: Re: [PATCH 11/12] AppArmor hooks to interface with the LSM, module parameters and initialization.

Serge E. Hallyn wrote:
> Quoting [email protected] ([email protected]):
>> From: John Johansen <[email protected]>
>>
>> Signed-off-by: John Johansen <[email protected]>
>> ---

<< snip >>

>> + */
>> +static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
>> +{
>> + /* freed by apparmor_cred_free */
>> + struct aa_task_cxt *cxt = aa_alloc_task_context(gfp);
>> + if (cxt)
>> + return -ENOMEM;
>
> if (!cxt)? :)
>
yikes, how the????!!!! Color me embarressed, I don't know how that
slipped in????

thanks
john

2010-02-23 08:31:12

by Tetsuo Handa

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

Regarding audit.c

53 static int aa_audit_base(int type, struct aa_profile *profile,
54 struct aa_audit *sa, struct audit_context *audit_cxt,
55 void (*cb) (struct audit_buffer *, struct aa_audit *))
(...snipped...)
93 pid_t pid = task->real_parent->pid;
I think you need to protect this dereference with RCU
(see SYSCALL_DEFINE0(getppid)).





Regarding domain.c

54 static int aa_may_change_ptraced_domain(struct task_struct *task,
55 struct aa_profile *to_profile)
(...snipped...)
71 /* not ptraced */
72 if (!tracer || unconfined(tracerp))
73 goto out;
unconfined() does not accept NULL.
What guarantees that tracer's profile != NULL?





197 static const char *separate_fqname(const char *fqname, const char **ns_name)(...snipped...)
201 if (fqname[0] == ':') {
202 *ns_name = fqname + 1; /* skip : */
203 name = *ns_name + strlen(*ns_name) + 1;
204 if (!*name)
This will go beyond \0 if fqname was ":" . Is fqname checked somewhere else?
205 name = NULL;





229 static struct aa_profile *x_to_profile(struct aa_profile *profile,
230 const char *name, u16 xindex)
(...snipped...)
297 out:
This label seems unused.
298 /* released by caller */
299 return new_profile;





308 int apparmor_bprm_set_creds(struct linux_binprm *bprm)
(...snipped...)
336 profile = aa_newest_version(cxt->profile);
profile == NULL if cxt->profile == NULL.
What guarantees that cxt->profile != NULL?





534 int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
(...snipped...)
565 /* find first matching hat */
566 for (i = 0; i < count && !hat; i++)
567 /* released below */
568 hat = aa_find_child(root, hats[i]);
569 if (!hat) {
570 if (!COMPLAIN_MODE(root) || permtest) {
571 sa.base.info = "hat not found";
572 if (list_empty(&root->base.profiles))
573 sa.base.error = -ECHILD;
574 else
575 sa.base.error = -ENOENT;
576 goto out;
577 }
578 /* freed below */
579 name = new_compound_name(root->base.hname, hats[0]);
Is this hats[0] and not hats[i - 1]?


You have a lot of

sa.base.info = ...;
sa.base.error = -E...;

Passing sa.base to functions might simplify the code.





Regarding file.c

61 static void file_audit_cb(struct audit_buffer *ab, struct aa_audit *va)
62 {
63 struct aa_audit_file *sa = container_of(va, struct aa_audit_file, base);
64 u16 denied = sa->request & ~sa->perms.allowed;
65 uid_t fsuid;
66
67 if (sa->base.task)
68 fsuid = task_uid(sa->base.task);
Is task_struct pointed by sa->base.task guaranteed to exist until now?





Regarding lsm.c

135 static int apparmor_capable(struct task_struct *task, const struct cred *cred,
136 int cap, int audit)
137 {
138 struct aa_profile *profile;
139 /* cap_capable returns 0 on success, else -EPERM */
140 int error = cap_capable(task, cred, cap, audit);
141
142 profile = aa_cred_profile(cred);
aa_cred_profile() returns NULL but unconfined() and aa_capable() don't accept
profile == NULL case.
143 if (!error && !unconfined(profile))
144 error = aa_capable(task, profile, cap, audit);

For me, it is difficult to check "whether parameter may be NULL or not"
"whether function may return NULL or not".
Adding "Maybe NULL." / "Never NULL." to function parameter's comment line and
"May return NULL" / "Never returns NULL" for function's return value would be
helpful.

2010-02-23 08:38:52

by John Johansen

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

Tetsuo Handa wrote:
> Starting from apparmorfs.c and jumping randomly...
>
>
>
>
>
> 346 static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)(...snipped...)
> 369 /*
> 370 * verify: transition names string
> 371 */
> 372 for (c = j = 0; j < size - 1; j++) {
> 373 if (!str[j])
> 374 c++;
> 375 }
> 376 /* names beginning with : require an embedded \0 */
> 377 if (*str == ':' && c != 1)
> 378 goto fail;
> 379 /* fail - all other cases with embedded \0 */
> 380 else if (c)
> 381 goto fail;
> 382 profile->file.trans.table[i] = str;
> You need to "profile->file.trans.table[i] = str;" before "goto fail;"
> in order to let "aa_free_domain_entries()" to kzfree().
>
>
sigh, yep. You know the depressing thing is I patched that once and seem
to have dropped the fix when I fiddled with the code, again. :(

>
>
>
> 333 static struct aa_namespace *aa_prepare_namespace(const char *name)
> 334 {
> 335 struct aa_namespace *ns, *root;
> 336
> 337 root = aa_current_profile()->ns;
> aa_current_profile() returns NULL if current_cred()->security->profile == NULL.
>
that should never happen anymore, see last comment

> 338
> 339 write_lock(&root->lock);
> 340 if (name)
> 341 /* released by caller */
> 342 ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));
> 343 else
> 344 /* released by caller */
> 345 ns = aa_get_namespace(root);
> The "if (!ns) { ... } " block is for only name != NULL case because
> aa_alloc_namespace(NULL) returns NULL. Thus, you might want to do like
>
err, no. It is only for if the name is specified and the profile couldn't be
found in
342 ns = aa_get_namespace(__aa_find_namespace(&root->sub_ns, name));

The other case of ns = aa_get_namespace(root); is guaranteed to succeed.
I should move the whole if (!ns) block under if (name)

> else {
> /* released by caller */
> ns = aa_get_namespace(root);
> goto out_unlock;
> }
>
> 369 }
> out_unlock:
> 370 write_unlock(&root->lock);
> 371
> 372 /* return ref */
> 373 return ns;
>
>
>
>
>
> 889 ssize_t aa_interface_replace_profiles(void *udata, size_t size, bool add_only)
> (...snipped...)
> 946 /* must be cleared as it is shared with replaced-by */
> 947 kzfree(new_profile->rename);
> 948 new_profile->rename = NULL;
> Is it OK to clear now because replacement may not be taken place due to
> aa_audit_iface() returning -ENOMEM?
>
yes. The rename field is only used at this one point and the rename profile,
if found is used. However if the rename fails the name of what was to be renamed isn't currently audited and it really should be.

So this should be moved and used as part of auditing.

>
>
>
>
> 108 static const char *hname_tail(const char *hname)
> 109 {
> 110 char *split;
> 111 /* check for namespace which begins with a : and ends with : or \0 */
> 112 hname = strstrip((char *)hname);
> Oops. strstrip() has gone in 2.6.33 .
>
Hrmm, I had missed that. It exists after a fashion as front for strim but
I missed that on my .33 build

>
>
>
>
> 28 static void *kvmalloc(size_t size)
> 29 {
> I think you should return NULL for size == 0.
> kmalloc(0, GFP_KERNEL) returns ZERO_SIZE_PTR, which results in
> copy_from_user(ZERO_SIZE_PTR, userbuf, (size_t) -1)
> at aa_simple_write_to_buffer() if aa_profile_remove() got ((size_t) -1)
> for "size" parameter. This will cause problem if access_ok() check inside
> copy_from_user() is "return 1;".
> 30 void *buffer = kmalloc(size, GFP_KERNEL);
> 31 if (!buffer)
> 32 buffer = vmalloc(size);
> 33 return buffer;
> 34 }
>
agreed, will do

>
>
>
> 1003 ssize_t aa_interface_remove_profiles(char *fqname, size_t size)
> (...snipped...)
> 1027 /* ref count held by cred */
> 1028 root = aa_current_profile()->ns;
> aa_current_profile() may return NULL.
>
see below

>
> 278 static void *p_start(struct seq_file *f, loff_t *pos)
> 279 __acquires(root->lock)
> 280 {
> 281 struct aa_profile *profile = NULL;
> 282 struct aa_namespace *root = aa_current_profile()->ns;
> aa_current_profile() may return NULL.

No, it shouldn't anymore. That is one of the cleanups this round,
I got rid of the use of NULL to represent the unconfined profile.
Previously you would get a mix of "unfilter profiles" where the
unconfined profile was used and post filtered where NULL was used
to represent unconfined. This was because NULL used to be used
to represent unconfined everywhere but with profile namespaces
we need to carry which unconfined profile it is. Getting rid of
the mixed use allowed for several cleanups and made the code
easier to read too.

However I do notice that I missed cleaning up the comment for
aa_current_profile. I will make another pass at all the comments
and see if I missed anything else.

I will also up the policy/context comments to note that the
context should not ever have a NULL value for profile. It should
either be pointing to a confining profile or to the unconfined
profile for the namespace.

As always, thanks for the review Tetsuo

john

2010-02-23 09:17:15

by John Johansen

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

Tetsuo Handa wrote:
> Regarding audit.c
>
> 53 static int aa_audit_base(int type, struct aa_profile *profile,
> 54 struct aa_audit *sa, struct audit_context *audit_cxt,
> 55 void (*cb) (struct audit_buffer *, struct aa_audit *))
> (...snipped...)
> 93 pid_t pid = task->real_parent->pid;
> I think you need to protect this dereference with RCU
> (see SYSCALL_DEFINE0(getppid)).
>
yep

>
>
>
>
> Regarding domain.c
>
> 54 static int aa_may_change_ptraced_domain(struct task_struct *task,
> 55 struct aa_profile *to_profile)
> (...snipped...)
> 71 /* not ptraced */
> 72 if (!tracer || unconfined(tracerp))
> 73 goto out;
> unconfined() does not accept NULL.
> What guarantees that tracer's profile != NULL?
>
every context needs to have a valid profile now. That change happened when
AppArmor made the switch to creds, but internally the change wasn't originally rolled into all the code so NULL was used in a lot of places
for the unconfined profile. That should be finally cleaned up in this
submission.

>
>
>
>
> 197 static const char *separate_fqname(const char *fqname, const char **ns_name)(...snipped...)
> 201 if (fqname[0] == ':') {
> 202 *ns_name = fqname + 1; /* skip : */
> 203 name = *ns_name + strlen(*ns_name) + 1;
> 204 if (!*name)
> This will go beyond \0 if fqname was ":" . Is fqname checked somewhere else?
> 205 name = NULL;
>
yes. unpack_trans_table in policy_unpack.c verifies the transition table
strings. But thanks for asking because its check isn't quite right.

>
>
>
>
> 229 static struct aa_profile *x_to_profile(struct aa_profile *profile,
> 230 const char *name, u16 xindex)
> (...snipped...)
> 297 out:
> This label seems unused.
> 298 /* released by caller */
> 299 return new_profile;
>
well it is used by
case AA_X_NAME:
if (xindex & AA_X_CHILD)
/* released by caller */
new_profile = aa_find_attach(ns,
&profile->base.profiles,
name);
else
/* released by caller */
new_profile = aa_find_attach(ns, &ns->base.profiles,
name);

goto out;

but that goto could be replaced by a return

>
>
>
>
> 308 int apparmor_bprm_set_creds(struct linux_binprm *bprm)
> (...snipped...)
> 336 profile = aa_newest_version(cxt->profile);
> profile == NULL if cxt->profile == NULL.
> What guarantees that cxt->profile != NULL?
>
Well every cxt is supposed to point to a valid profile and fn's should fail
if they violate this. That being said there really should be some BUG_ONs
in the code for this, just like there is for the cxt.

>
>
>
>
> 534 int aa_change_hat(const char *hats[], int count, u64 token, bool permtest)
> (...snipped...)
> 565 /* find first matching hat */
> 566 for (i = 0; i < count && !hat; i++)
> 567 /* released below */
> 568 hat = aa_find_child(root, hats[i]);
> 569 if (!hat) {
> 570 if (!COMPLAIN_MODE(root) || permtest) {
> 571 sa.base.info = "hat not found";
> 572 if (list_empty(&root->base.profiles))
> 573 sa.base.error = -ECHILD;
> 574 else
> 575 sa.base.error = -ENOENT;
> 576 goto out;
> 577 }
> 578 /* freed below */
> 579 name = new_compound_name(root->base.hname, hats[0]);
> Is this hats[0] and not hats[i - 1]?
>
Yep, I know it seems strange and deserves a comment. This is an artifact
of what userspace is expecting as logging output. The traditional
behavior of learning mode is the first hat request to fail cause switching
into a learning profile and the failure is logged. If the requests succeeds
then the transition is made. At this point all requests have failed so
we log the first as expected.

There is a planned improvement here where we log more information but this
is sufficient for the moment.

>
> You have a lot of
>
> sa.base.info = ...;
> sa.base.error = -E...;
>
> Passing sa.base to functions might simplify the code.
>
Hrmm, yeah I have played around with this some and haven't hit on the right
combination yet.

>
>
>
>
> Regarding file.c
>
> 61 static void file_audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> 62 {
> 63 struct aa_audit_file *sa = container_of(va, struct aa_audit_file, base);
> 64 u16 denied = sa->request & ~sa->perms.allowed;
> 65 uid_t fsuid;
> 66
> 67 if (sa->base.task)
> 68 fsuid = task_uid(sa->base.task);
> Is task_struct pointed by sa->base.task guaranteed to exist until now?
>
well yes but only because sa->base.task isn't used :),


>
>
>
>
> Regarding lsm.c
>
> 135 static int apparmor_capable(struct task_struct *task, const struct cred *cred,
> 136 int cap, int audit)
> 137 {
> 138 struct aa_profile *profile;
> 139 /* cap_capable returns 0 on success, else -EPERM */
> 140 int error = cap_capable(task, cred, cap, audit);
> 141
> 142 profile = aa_cred_profile(cred);
> aa_cred_profile() returns NULL but unconfined() and aa_capable() don't accept
> profile == NULL case.
> 143 if (!error && !unconfined(profile))
> 144 error = aa_capable(task, profile, cap, audit);
>
> For me, it is difficult to check "whether parameter may be NULL or not"
> "whether function may return NULL or not".
> Adding "Maybe NULL." / "Never NULL." to function parameter's comment line and
> "May return NULL" / "Never returns NULL" for function's return value would be
> helpful.
okay, will do

2010-02-26 03:22:46

by Tetsuo Handa

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

Regarding path.c

"struct task_struct" has "struct fs_struct *fs" and "struct nsproxy *nsproxy".
The former is used for holding current process's root directory and current
directory.
The latter is used for holding current process's namespace information.

I'm not sure, but I think we can directly fetch current process's namespace's
root by dereferencing current->nsproxy->mnt_ns , and therefore simplify
d_namespace_path() as follows.

36 static int d_namespace_path(struct path *path, char *buf, int buflen,
37 char **name, int flags)
38 {
39 struct path root, tmp, ns_root = { };

/* Remove "ns_root" as only "root" and "tmp" are used. */

40 char *res;
41 int deleted, connected;
42 int error = 0;
43

if (flags & PATH_CHROOT_REL) {

44 read_lock(&current->fs->lock);
45 root = current->fs->root;
46 /* released below */
47 path_get(&root);
48 read_unlock(&current->fs->lock);
49

} else {
root.mnt = current->nsproxy->root;
root.dentry = root.mnt->mnt_root;
/* released below */
path_get(&root);
}

/*

50 spin_lock(&vfsmount_lock);
51 if (root.mnt && root.mnt->mnt_ns)
52 /* released below */
53 ns_root.mnt = mntget(root.mnt->mnt_ns->root);
54 if (ns_root.mnt)
55 /* released below */
56 ns_root.dentry = dget(ns_root.mnt->mnt_root);
57 spin_unlock(&vfsmount_lock);
58

*/

59 spin_lock(&dcache_lock);
60 /* There is a race window between path lookup here and the
61 * need to strip the " (deleted) string that __d_path applies
62 * Detect the race and relookup the path
63 *
64 * The stripping of (deleted) is a hack that could be removed
65 * with an updated __d_path
66 */
67 do {

/*

68 if (flags & PATH_CHROOT_REL)

*/

69 tmp = root;

/*

70 else
71 tmp = ns_root;

*/

72 deleted = d_unlinked(path->dentry);
73 res = __d_path(path, &tmp, buf, buflen);
74
75 } while (deleted != d_unlinked(path->dentry));
76 spin_unlock(&dcache_lock);
77
78 *name = res;
79 /* handle error conditions - and still allow a partial path to
80 * be returned.
81 */
82 if (IS_ERR(res)) {
83 error = PTR_ERR(res);
84 *name = buf;
85 goto out;
86 }
87 if (deleted) {
88 /* On some filesystems, newly allocated dentries appear to the
89 * security_path hooks as a deleted dentry except without an
90 * inode allocated.
91 *
92 * Remove the appended deleted text and return as string for
93 * normal mediation, or auditing. The (deleted) string is
94 * guarenteed to be added in this case, so just strip it.
95 */
96 buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */
97
98 if (path->dentry->d_inode && !(flags & PATH_MEDIATE_DELETED)) {
99 error = -ENOENT;
100 goto out;
101 }
102 }
103

/*

104 if (flags & PATH_CHROOT_REL)

*/

105 connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;

/*

106 else
107 connected = tmp.dentry == ns_root.dentry &&
108 tmp.mnt == ns_root.mnt;

*/

109
110 if (!connected &&
111 !(flags & PATH_CONNECT_PATH) &&
112 !((flags & PATH_CHROOT_REL) && (flags & PATH_CHROOT_NSCONNECT) &&
113 (tmp.dentry == ns_root.dentry && tmp.mnt == ns_root.mnt))) {

/* Change from "ns_root" to "root". */

114 /* disconnected path, don't return pathname starting with '/' */
115 error = -ESTALE;
116 if (*res == '/')
117 *name = res + 1;
118 }
119
120 out:
121 path_put(&root);

/*

122 path_put(&ns_root);

*/

123
124 return error;
125 }

2010-02-26 06:31:33

by Tetsuo Handa

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

Regarding match.c

33 static struct table_header *unpack_table(void *blob, size_t bsize)
(...snipped...)
48 blob += sizeof(struct table_header);
Adding against "void *" is gcc specific.

54 tsize = table_size(th.td_lolen, th.td_flags);
Is tsize != 0 (i.e. integer overflow never happen) guaranteed?





188 struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags)
(...snipped...)
215 while (size > 0) {
216 struct table_header *table;
217 table = unpack_table(blob, size);
218 if (!table)
219 goto fail;
220

You need to do

if (table->td_id < YYTD_ID_TSIZE)
dfa->tables[table->td_id] = table;

now in order to let aa_dfa_free(dfa) to free_table().
You might rather want to do

if (table->td_id < YYTD_ID_TSIZE && !dfa->tables[table->td_id])
dfa->tables[table->td_id] = table;
else
goto fail;

in case duplicated entries are found.

221 switch (table->td_id) {
222 case YYTD_ID_ACCEPT:
223 if (!(table->td_flags & ACCEPT1_FLAGS(flags)))
224 goto fail;
225 break;
226 case YYTD_ID_ACCEPT2:
227 if (!(table->td_flags & ACCEPT2_FLAGS(flags)))
228 goto fail;
229 break;
230 case YYTD_ID_BASE:
231 if (table->td_flags != YYTD_DATA32)
232 goto fail;
233 break;
234 case YYTD_ID_DEF:
235 case YYTD_ID_NXT:
236 case YYTD_ID_CHK:
237 if (table->td_flags != YYTD_DATA16)
238 goto fail;
239 break;
240 case YYTD_ID_EC:
241 if (table->td_flags != YYTD_DATA8)
242 goto fail;
243 break;
244 default:
245 free_table(table);

This "free_table(table);" is not needed as aa_dfa_free(dfa) will do it.

246 goto fail;
247 }
248 dfa->tables[table->td_id] = table;

This "dfa->tables[table->td_id] = table;" is too late.

249 blob += table_size(table->td_lolen, table->td_flags);
250 size -= table_size(table->td_lolen, table->td_flags);
251 }





Regarding policy.c

606 struct aa_profile *aa_new_null_profile(struct aa_profile *parent, int hat)
607 {
608 struct aa_profile *profile = NULL;
609 char *name;
610 u32 sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
611
612 /* freed below */
613 name = kmalloc(strlen(parent->base.hname) + 2 + 7 + 8, GFP_KERNEL);
614 if (!name)
615 goto fail;
616 sprintf(name, "%s//null-%x", parent->base.hname, sid);
617
618 profile = aa_alloc_profile(name);
619 kfree(name);
620 if (!profile)
621 goto fail;
622
623 profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);

Why calling aa_alloc_sid(AA_ALLOC_SYS_SID) again rather than using sid?