Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Patch 0/12] AppArmor security module

3 views
Skip to first unread message

John Johansen

unread,
Nov 3, 2009, 6:48:52 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
This is the newest version of the AppArmor security module it has been
rewritten to use the security_path hooks instead of the previous vfs
approach. The current implementation is aimed at being as semantically
close to previous versions of AppArmor as possible while using the
existing LSM infrastructure.

AppArmor is a wip and is roughly equivalent to previous versions with better
control of exec transitions. Development is on going and improvements to
file, capability, network, resource usage and ipc mediation are planned.


In brief AppArmor is a security module that uses a white list to determine
permissions. It currently provides rules for file, capability, resource
and basic network mediation. With its file mediation using path name based
pattern matching.

Though it is possible to confine an entire system, AppArmor by design allows
for application based mediation where only a subset of a running system is
confined. Any process that is not confined by AppArmor is only restricted
by the standard unix DAC permission model.


_Issues Addressed Since Last Time AppArmor was Posted (LSM only)_
* all issues raised have been address except for return an error out
of the accept hook which is not a bug but could removed under the
current simple network mediation model. It was decided instead that
any development time on addressing this should go towards the new
network controls instead.
* The code has seen further cleanups, and has been run through lindent
and checkpatch again (its been awhile).
* several bugs have been addressed
- change_hat auditing
- quieting of file auditing
- policy load failures
- mediation of creation failure under some filesystems

A brief summary of AppArmor is below

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

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


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

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

Profiles

AppArmor's base unit of confinement is a profile, which defines the
access permissions for tasks it is attached to. Profiles are grouped in
to profile namespaces, and must have a unique name within the namespace.

Profile names provide context for when a profile should be used and
may determine the attachment of a profile to an application. If a profile
name begins with a / character its name is considered to be a path name
and it may be matched against executable names to determine attachment.
Profile names that do not begin with a / character are not considered
during automatic profile attachment.

Profile names that begin with / characters can contain AppArmor pattern
matching and may match against multiple executables. If multiple
profiles match an executable then the profile with the longest left
exact match wins. If the winner can not be determined execution of the
task will fail.

Profile names that begin with / characters are consider for attachment
when an unconfined application calls exec, or when a confined application
uses a exec rules specifying that such a match should be done (px, cx).
They may also be attached using the change_profile, or change_hat directives.

Profile's names that don't begin with a / character are only attached
when they are specified by a profile exec transition, or through using
that change_profile, change_hat directives.

A partial sample profile

/usr/sbin/ntpd {
#include <abstractions/base>
#include <abstractions/nameservice>

capability ipc_lock,
capability net_bind_service,
capability setgid,
capability setuid,
capability sys_chroot,
capability sys_resource,
capability sys_time,

/drift/ntp.drift rwl,
/drift/ntp.drift.TEMP rwl,
/etc/ntp.conf r,
/etc/ntp/drift* rwl,
/etc/ntp/keys r,

...
}


AppArmor allows for rules that black list permissions by prepending the deny
keyword, these rules are used to annotate known items that will be encountered
and should be rejected.

eg.
deny /etc/shadow w,


please refer to the documentation link for further information


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majo...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

John Johansen

unread,
Nov 3, 2009, 6:49:58 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
Miscellaneous functions and defines needed by AppArmor, including
the base path resolution routines.

Signed-off-by: John Johansen <john.j...@canonical.com>
---
security/apparmor/include/apparmor.h | 61 ++++++++++++
security/apparmor/include/path.h | 21 ++++
security/apparmor/lib.c | 76 +++++++++++++++
security/apparmor/path.c | 177 ++++++++++++++++++++++++++++++++++
4 files changed, 335 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/apparmor.h
create mode 100644 security/apparmor/include/path.h
create mode 100644 security/apparmor/lib.c
create mode 100644 security/apparmor/path.c

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

John Johansen

unread,
Nov 3, 2009, 6:50:06 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
AppArmor contexts attach profiles and state to tasks, files, etc. when
a direct profile reference is not sufficient.

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/apparmor/context.c | 224 +++++++++++++++++++++++++++++++++++
security/apparmor/include/context.h | 146 +++++++++++++++++++++++
2 files changed, 370 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/context.c
create mode 100644 security/apparmor/include/context.h

diff --git a/security/apparmor/context.c b/security/apparmor/context.c
new file mode 100644
index 0000000..81cc089
--- /dev/null
+++ b/security/apparmor/context.c
@@ -0,0 +1,224 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor functions used to manipulate object security
+ * contexts.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include "include/context.h"
+#include "include/policy.h"
+
+struct aa_task_context *aa_alloc_task_context(gfp_t flags)
+{
+ return kzalloc(sizeof(struct aa_task_context), flags);
+}
+
+void aa_free_task_context(struct aa_task_context *cxt)
+{
+ if (cxt) {
+ aa_put_profile(cxt->sys.profile);
+ aa_put_profile(cxt->sys.previous);
+ aa_put_profile(cxt->sys.onexec);
+
+ kzfree(cxt);
+ }
+}
+
+/*
+ * duplicate a task context, incrementing reference counts
+ */
+struct aa_task_context *aa_dup_task_context(struct aa_task_context *old_cxt,
+ gfp_t gfp)
+{
+ struct aa_task_context *cxt;
+
+ cxt = kmemdup(old_cxt, sizeof(*cxt), gfp);
+ if (!cxt)
+ return NULL;
+
+ aa_get_profile(cxt->sys.profile);
+ aa_get_profile(cxt->sys.previous);
+ aa_get_profile(cxt->sys.onexec);
+
+ return cxt;
+}
+
+/**
+ * aa_cred_policy - obtain cred's profiles
+ * @cred: cred to obtain profiles from
+ * @sys: return system profile
+ * does NOT increment reference count
+ */
+void aa_cred_policy(const struct cred *cred, struct aa_profile **sys)
+{
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+ *sys = aa_filtered_profile(aa_profile_newest(cxt->sys.profile));
+}
+
+/**
+ * aa_get_task_policy - get the cred with the task policy, and current profiles
+ * @task: task to get policy of
+ * @sys: return - pointer to system profile
+ *
+ * Only gets the cred ref count which has ref counts on the profiles returned
+ */
+struct cred *aa_get_task_policy(const struct task_struct *task,
+ struct aa_profile **sys)
+{
+ struct cred *cred = get_task_cred(task);
+ aa_cred_policy(cred, sys);
+ return cred;
+}
+
+void aa_put_task_policy(struct cred *cred)
+{
+ put_cred(cred);
+}
+
+static void replace_group(struct aa_task_cxt_group *cgrp,
+ struct aa_profile *profile)
+{
+ if (cgrp->profile == profile)
+ return;
+
+ if (!profile || (profile->flags & PFLAG_UNCONFINED) ||
+ (cgrp->profile && cgrp->profile->ns != profile->ns)) {
+ aa_put_profile(cgrp->previous);
+ aa_put_profile(cgrp->onexec);
+ cgrp->previous = NULL;
+ cgrp->onexec = NULL;
+ cgrp->token = 0;
+ }
+ aa_put_profile(cgrp->profile);
+ cgrp->profile = aa_get_profile(profile);
+}
+
+/**
+ * aa_replace_current_profiles - replace the current tasks profiles
+ * @sys: new system profile
+ *
+ * Returns: error on failure
+ */
+int aa_replace_current_profiles(struct aa_profile *sys)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ replace_group(&cxt->sys, sys);
+
+ commit_creds(new);


+ return 0;
+}
+

+/**
+ * aa_set_current_onexec - set the tasks change_profile to happen onexec
+ * @sys: system profile to set at exec
+ *
+ * Returns: error on failure
+ */
+int aa_set_current_onexec(struct aa_profile *sys)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = aa_get_profile(sys);
+
+ commit_creds(new);


+ return 0;
+}
+

+/**
+ * aa_set_current_hat - set the current tasks hat
+ * @profile: profile to set as the current hat
+ * @token: token value that must be specified to change from the hat
+ *
+ * Do switch of tasks hat. If the task is currently in a hat
+ * validate the token to match.
+ *
+ * Returns: error on failure
+ */
+int aa_set_current_hat(struct aa_profile *profile, u64 token)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ if (!cxt->sys.previous) {
+ cxt->sys.previous = cxt->sys.profile;
+ cxt->sys.token = token;
+ } else if (cxt->sys.token == token) {
+ aa_put_profile(cxt->sys.profile);
+ } else {
+ /* previous_profile && cxt->token != token */
+ abort_creds(new);
+ return -EACCES;
+ }
+ cxt->sys.profile = aa_get_profile(profile);
+ /* clear exec on switching context */
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = NULL;
+
+ commit_creds(new);


+ return 0;
+}
+

+/**
+ * aa_restore_previous_profile - exit from hat context restoring the profile
+ * @token: the token that must be matched to exit hat context
+ *
+ * Attempt to return out of a hat to the previous profile. The token
+ * must match the stored token value.
+ *
+ * Returns: error of failure
+ */
+int aa_restore_previous_profile(u64 token)
+{
+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)
+ return -ENOMEM;
+
+ cxt = new->security;
+ if (cxt->sys.token != token) {
+ abort_creds(new);
+ return -EACCES;
+ }
+ /* ignore restores when there is no saved profile */
+ if (!cxt->sys.previous) {
+ abort_creds(new);


+ return 0;
+ }
+

+ aa_put_profile(cxt->sys.profile);
+ cxt->sys.profile = aa_profile_newest(cxt->sys.previous);
+ if (unlikely(cxt->sys.profile != cxt->sys.previous)) {
+ aa_get_profile(cxt->sys.profile);
+ aa_put_profile(cxt->sys.previous);
+ }
+ /* clear exec && prev information when restoring to previous context */
+ cxt->sys.previous = NULL;
+ cxt->sys.token = 0;
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = NULL;
+
+ commit_creds(new);
+ return 0;
+}
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h
new file mode 100644
index 0000000..5f30f31
--- /dev/null
+++ b/security/apparmor/include/context.h
@@ -0,0 +1,146 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor contexts used to associate "labels" to objects.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_CONTEXT_H
+#define __AA_CONTEXT_H
+
+#include <linux/cred.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+
+#include "policy.h"
+
+/* struct aa_file_cxt - the AppArmor context the file was opened in
+ * @profile: the profile the file was opened under
+ * @perms: the permission the file was opened with
+ */
+struct aa_file_cxt {
+ struct aa_profile *profile;
+ u16 allowed;
+};
+
+static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
+{
+ return kzalloc(sizeof(struct aa_file_cxt), gfp);
+}
+
+static inline void aa_free_file_context(struct aa_file_cxt *cxt)
+{
+ aa_put_profile(cxt->profile);
+ memset(cxt, 0, sizeof(struct aa_file_cxt));
+ kfree(cxt);
+}
+
+/* struct aa_task_cxt_group - a grouping label data for confined tasks
+ * @profile: the current profile
+ * @exec: profile to transition to on next exec
+ * @previous: profile the task may return to
+ * @token: magic value the task must know for returning to @previous_profile
+ *
+ * Contains the task's current profile (which could change due to
+ * change_hat). Plus the hat_magic needed during change_hat.
+ */
+struct aa_task_cxt_group {
+ struct aa_profile *profile;
+ struct aa_profile *onexec;
+ struct aa_profile *previous;
+ u64 token;
+};
+
+/**
+ * struct aa_task_context - primary label for confined tasks
+ * @sys: the system labeling for the task
+ *
+ * A task is confined by the intersection of its system and user profiles
+ */
+struct aa_task_context {
+ struct aa_task_cxt_group sys;
+};
+
+struct aa_task_context *aa_alloc_task_context(gfp_t flags);
+void aa_free_task_context(struct aa_task_context *cxt);
+struct aa_task_context *aa_dup_task_context(struct aa_task_context *old_cxt,
+ gfp_t gfp);
+void aa_cred_policy(const struct cred *cred, struct aa_profile **sys);
+struct cred *aa_get_task_policy(const struct task_struct *task,
+ struct aa_profile **sys);
+int aa_replace_current_profiles(struct aa_profile *sys);
+void aa_put_task_policy(struct cred *cred);
+int aa_set_current_onexec(struct aa_profile *sys);
+int aa_set_current_hat(struct aa_profile *profile, u64 token);
+int aa_restore_previous_profile(u64 cookie);
+
+static inline struct aa_task_context *__aa_task_cxt(struct task_struct *task)
+{
+ return __task_cred(task)->security;
+}
+
+/**
+ * __aa_task_is_confined - determine if @task has any confinement
+ * @task: task to check confinement of
+ *
+ * If @task != current needs to be in RCU safe critical section
+ */
+static inline int __aa_task_is_confined(struct task_struct *task)
+{
+ struct aa_task_context *cxt;
+ int rc = 1;
+
+ cxt = __aa_task_cxt(task);
+ if (!cxt || (cxt->sys.profile->flags & PFLAG_UNCONFINED))
+ rc = 0;
+
+ return rc;
+}
+
+static inline const struct cred *aa_current_policy(struct aa_profile **sys)
+{
+ const struct cred *cred = current_cred();
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+ *sys = aa_filtered_profile(aa_profile_newest(cxt->sys.profile));
+
+ return cred;
+}
+
+static inline const struct cred *aa_current_policy_wupd(struct aa_profile **sys)
+{
+ const struct cred *cred = current_cred();
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+
+ *sys = aa_profile_newest(cxt->sys.profile);
+ if (unlikely((cxt->sys.profile != *sys)))
+ aa_replace_current_profiles(*sys);
+ *sys = aa_filtered_profile(*sys);
+
+ return cred;
+}
+
+static inline struct aa_profile *aa_current_profile(void)
+{
+ const struct cred *cred = current_cred();
+ struct aa_task_context *cxt = cred->security;
+ BUG_ON(!cxt);
+ return aa_filtered_profile(aa_profile_newest(cxt->sys.profile));
+}
+
+static inline struct aa_profile *aa_current_profile_wupd(void)
+{
+ struct aa_profile *p;
+ aa_current_policy_wupd(&p);
+ return p;
+}
+
+#endif /* __AA_CONTEXT_H */
--
1.6.3.3

John Johansen

unread,
Nov 3, 2009, 6:50:10 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
The basic routines and defines for AppArmor policy. AppArmor policy
is defined by a few basic components.
profiles - the basic unit of confinement contain all the information
to enforce policy on a task

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

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/apparmor/include/policy.h | 296 ++++++++++++++++
security/apparmor/include/sid.h | 45 +++
security/apparmor/policy.c | 678 ++++++++++++++++++++++++++++++++++++
security/apparmor/sid.c | 108 ++++++
4 files changed, 1127 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..6274b82
--- /dev/null
+++ b/security/apparmor/include/policy.h
@@ -0,0 +1,296 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor policy definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_POLICY_H
+#define __AA_POLICY_H
+
+#include <linux/capability.h>
+#include <linux/cred.h>
+#include <linux/kref.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+
+#include "apparmor.h"
+#include "audit.h"
+#include "capability.h"
+#include "domain.h"
+#include "file.h"
+#include "net.h"
+#include "resource.h"
+
+extern const char *profile_mode_names[];
+#define APPARMOR_NAMES_MAX_INDEX 3
+
+#define PROFILE_COMPLAIN(_profile) \
+ ((aa_g_profile_mode == APPARMOR_COMPLAIN) || ((_profile) && \
+ (_profile)->mode == APPARMOR_COMPLAIN))
+
+#define PROFILE_KILL(_profile) \
+ ((aa_g_profile_mode == APPARMOR_KILL) || ((_profile) && \
+ (_profile)->mode == APPARMOR_KILL))
+
+#define PROFILE_IS_HAT(_profile) \
+ ((_profile) && (_profile)->flags & PFLAG_HAT)
+
+/*
+ * FIXME: currently need a clean way to replace and remove profiles as a
+ * set. It should be done at the namespace level.
+ * Either, with a set of profiles loaded at the namespace level or via
+ * a mark and remove marked interface.
+ */
+enum profile_mode {
+ APPARMOR_ENFORCE, /* enforce access rules */
+ APPARMOR_COMPLAIN, /* allow and log access violations */
+ APPARMOR_KILL, /* kill task on access violation */
+};
+
+enum profile_flags {
+ PFLAG_HAT = 1, /* profile is a hat */
+ PFLAG_UNCONFINED = 2, /* profile is the unconfined profile */
+ PFLAG_NULL = 4, /* profile is null learning profile */
+ PFLAG_IX_ON_NAME_ERROR = 8, /* fallback to ix on name lookup fail */
+ PFLAG_IMMUTABLE = 0x10, /* don't allow changes/replacement */
+ PFLAG_USER_DEFINED = 0x20, /* user based profile */
+ PFLAG_NO_LIST_REF = 0x40, /* list doesn't keep profile ref */
+ PFLAG_MMAP_MIN_ADDR = 0x80, /* profile controls mmap_min_addr */
+ PFLAG_DELETED_NAMES = 0x100, /* mediate deleted paths */
+ PFLAG_CONNECT_PATH = 0x200, /* connect disconnected paths to / */
+};
+
+#define AA_NEW_SID 0
+
+struct aa_profile;
+
+/* struct aa_policy_common - common part of both namespaces and profiles
+ * @name: name of the object
+ * @count: reference count of the obj
+ * lock: lock for modifying the object
+ * @list: list object is on
+ * @profiles: head of the profiles list contained in the object
+ */
+struct aa_policy_common {
+ char *name;
+ struct kref count;
+ rwlock_t lock;
+ struct list_head list;
+ struct list_head profiles;
+};
+
+/* struct aa_ns_acct - accounting of profiles in namespace
+ * @max_size: maximum space allowed for all profiles in namespace
+ * @max_count: maximum number of profiles that can be in this namespace
+ * @size: current size of profiles
+ * @count: current count of profiles (includes null profiles)
+ */
+struct aa_ns_acct {
+ int max_size;
+ int max_count;
+ int size;
+ int count;
+};
+
+/* struct aa_namespace - namespace for a set of profiles
+ * @name: the name of the namespace
+ * @list: list the namespace is on
+ * @profiles: list of profile in the namespace
+ * @acct: accounting for the namespace
+ * @profile_count: count of profiles on @profiles list
+ * @size: accounting of how much memory is consumed by the contained profiles
+ * @unconfined: special unconfined profile for the namespace
+ * @count: reference count on the namespace
+ * @lock: lock for adding/removing profile to the namespace
+ *
+ * An aa_namespace defines the set profiles that are searched to determine
+ * which profile to attach to a task. Profiles can not be shared between
+ * aa_namespaces and profile names within a namespace are guarenteed to be
+ * unique. When profiles in seperate namespaces have the same name they
+ * are NOT considered to be equivalent.
+ *
+ * Namespace names must be unique and can not contain the characters :/\0
+ *
+ * FIXME TODO: add vserver support so a vserer gets a default namespace
+ */
+struct aa_namespace {
+ struct aa_policy_common base;
+ struct aa_ns_acct acct;
+ int is_stale;
+ struct aa_profile *unconfined;
+};
+
+/* struct aa_profile - basic confinement data
+ * @base - base componets of the profile (name, refcount, lists, lock ...)
+ * @fqname - The fully qualified profile name, less the namespace name
+ * @ns: namespace the profile is in
+ * @parent: parent profile of this profile, if one exists
+ * @replacedby: is set profile that replaced this profile
+ * @xmatch: optional extended matching for unconfined executables names
+ * @xmatch_plen: xmatch prefix len, used to determine xmatch priority
+ * @sid: the unique security id number of this profile
+ * @audit: the auditing mode of the profile
+ * @mode: the enforcement mode of the profile
+ * @flags: flags controlling profile behavior
+ * @size: the memory consumed by this profiles rules
+ * @file: The set of rules governing basic file access and domain transitions
+ * @caps: capabilities for the profile
+ * @net: network controls for the profile
+ * @rlimits: rlimits for the profile
+ *
+ * The AppArmor profile contains the basic confinement data. Each profile
+ * has a name, and exist in a namespace. The @name and @exec_match are
+ * used to determine profile attachment against unconfined tasks. All other
+ * attachments are determined by in profile X transition rules.
+ *
+ * The @replacedby field is write protected by the profile lock. Reads
+ * are assumed to be atomic, and are done without locking.
+ *
+ * Profiles have a hierachy where hats and children profiles keep
+ * a reference to their parent.
+ *
+ * Profile names can not begin with a : and can not contain the \0
+ * character. If a profile name begins with / it will be considered when
+ * determining profile attachment on "unconfined" tasks.
+ */
+struct aa_profile {
+ struct aa_policy_common base;
+ char *fqname;
+
+ struct aa_namespace *ns;
+ struct aa_profile *parent;
+ struct aa_profile *replacedby;
+
+ struct aa_dfa *xmatch;
+ int xmatch_len;
+ u32 sid;
+ enum audit_mode audit;
+ enum profile_mode mode;
+ u32 flags;
+ int size;
+
+ unsigned long mmap_min_addr;
+
+ struct aa_file_rules file;
+ struct aa_caps caps;
+ struct aa_net net;
+ struct aa_rlimit rlimits;
+};
+
+extern struct list_head ns_list;
+extern rwlock_t ns_list_lock;
+
+extern struct aa_namespace *default_namespace;
+extern enum profile_mode aa_g_profile_mode;
+
+void aa_add_profile(struct aa_policy_common *common,


+ struct aa_profile *profile);
+

+int aa_alloc_default_namespace(void);
+void aa_free_default_namespace(void);
+struct aa_namespace *alloc_aa_namespace(const char *name);
+void free_aa_namespace_kref(struct kref *kref);
+void free_aa_namespace(struct aa_namespace *ns);
+struct aa_namespace *__aa_find_namespace(struct list_head *head,
+ const char *name);
+
+struct aa_namespace *aa_find_namespace(const char *name);
+struct aa_namespace *aa_prepare_namespace(const char *name);
+void aa_remove_namespace(struct aa_namespace *ns);
+struct aa_namespace *aa_prepare_namespace(const char *name);
+void aa_profile_list_release(struct list_head *head);
+void aa_profile_ns_list_release(void);
+void __aa_remove_namespace(struct aa_namespace *ns);
+
+static inline struct aa_policy_common *aa_get_common(struct aa_policy_common *c)
+{
+ if (c)
+ kref_get(&c->count);
+
+ return c;
+}
+
+static inline struct aa_namespace *aa_get_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ kref_get(&(ns->base.count));
+
+ return ns;
+}
+
+static inline void aa_put_namespace(struct aa_namespace *ns)
+{
+ if (ns)
+ kref_put(&ns->base.count, free_aa_namespace_kref);
+}
+
+struct aa_profile *alloc_aa_profile(const char *name);
+struct aa_profile *aa_alloc_null_profile(struct aa_profile *parent, int hat);
+void free_aa_profile_kref(struct kref *kref);
+void free_aa_profile(struct aa_profile *profile);
+struct aa_profile *__aa_find_profile(struct list_head *head, const char *name);
+struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name);
+struct aa_policy_common *__aa_find_parent_by_fqname(struct aa_namespace *ns,
+ const char *fqname);
+struct aa_profile *__aa_find_profile_by_fqname(struct aa_namespace *ns,
+ const char *fqname);
+struct aa_profile *aa_find_profile_by_fqname(struct aa_namespace *ns,
+ const char *name);
+struct aa_profile *aa_match_profile(struct aa_namespace *ns, const char *name);
+struct aa_profile *aa_profile_newest(struct aa_profile *profile);
+void __aa_add_profile(struct aa_policy_common *common,
+ struct aa_profile *profile);
+void __aa_remove_profile(struct aa_profile *profile,
+ struct aa_profile *replacement);
+void __aa_replace_profile(struct aa_profile *profile,
+ struct aa_profile *replacement);
+void __aa_profile_list_release(struct list_head *head);
+
+static inline struct aa_profile *aa_filtered_profile(struct aa_profile *profile)
+{
+ if (profile->flags & PFLAG_UNCONFINED)
+ return NULL;
+ 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, free_aa_profile_kref);
+}
+
+static inline int PROFILE_AUDIT_MODE(struct aa_profile *profile)
+{
+ if (aa_g_audit != AUDIT_NORMAL)
+ return aa_g_audit;
+ if (profile)
+ return profile->audit;
+ return AUDIT_NORMAL;
+}
+
+#endif /* __AA_POLICY_H */
diff --git a/security/apparmor/include/sid.h b/security/apparmor/include/sid.h
new file mode 100644
index 0000000..0f5df2f
--- /dev/null
+++ b/security/apparmor/include/sid.h
@@ -0,0 +1,45 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor security identifier (sid) definitions
+ *


+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_SID_H
+#define __AA_SID_H
+
+#include <linux/types.h>
+
+struct aa_profile;
+
+#define AA_ALLOC_USR_SID 1
+#define AA_ALLOC_SYS_SID 0
+
+u32 aa_alloc_sid(int is_usr);
+void aa_free_sid(u32 sid);
+int aa_add_sid_profile(u32 sid, struct aa_profile *profile);
+int aa_replace_sid_profile(u32 sid, struct aa_profile *profile);
+struct aa_profile *aa_get_sid_profile(u32 sid);
+
+static inline u32 aa_compound_sid(u32 sys, u32 usr)
+{
+ return sys | usr;
+}
+
+static inline u32 aa_usr_sid(u32 sid)
+{
+ return sid & 0xffff0000;
+}
+
+static inline u32 aa_sys_sid(u32 sid)
+{
+ return sid & 0xffff;
+}
+
+#endif /* __AA_SID_H */
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
new file mode 100644
index 0000000..4dbfaa2
--- /dev/null
+++ b/security/apparmor/policy.c
@@ -0,0 +1,678 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor policy manipulation functions


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *

+ *
+ * AppArmor policy is based around profiles, which contain the rules a
+ * task is confined by. Every task in the sytem has a profile attached
+ * to it determined either by matching "unconfined" tasks against the
+ * visible set of profiles or by following a profiles attachment rules.
+ *
+ * Each profile exists in an AppArmor profile namespace which is a
+ * container of related profiles. Each namespace contains a special
+ * "unconfined" profile, which doesn't efforce 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 name not start with : or @ and may not contain \0
+ * a // in a profile name indicates a compound name with the name before
+ * the // being the parent profile and the name after the child
+ *
+ * Reserved profile names
+ * unconfined - special automatically generated unconfined profile
+ * inherit - special name to indicate profile inheritance
+ * null-XXXX-YYYY - special automically generated learning profiles
+ *
+ * Namespace names may not start with / or @ and may not contain \0 or /
+ * Reserved namespace namespace
+ * default - the default namespace setup by AppArmor
+ * user-XXXX - user defined profiles


+ */
+
+#include <linux/slab.h>

+#include <linux/spinlock.h>
+#include <linux/string.h>
+
+#include "include/apparmor.h"
+#include "include/capability.h"
+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/match.h"
+#include "include/policy.h"
+#include "include/resource.h"
+#include "include/sid.h"
+
+/* list of profile namespaces and lock */
+LIST_HEAD(ns_list);
+DEFINE_RWLOCK(ns_list_lock);
+
+struct aa_namespace *default_namespace;
+
+const char *profile_mode_names[] = {
+ "enforce",
+ "complain",
+ "kill",
+};
+
+static int common_init(struct aa_policy_common *common, const char *name)
+{
+ common->name = kstrdup(name, GFP_KERNEL);
+ if (!common->name)
+ return 0;
+ INIT_LIST_HEAD(&common->list);
+ INIT_LIST_HEAD(&common->profiles);
+ kref_init(&common->count);
+ rwlock_init(&common->lock);
+
+ return 1;
+}
+
+static void common_free(struct aa_policy_common *common)
+{
+ /* still contains profiles -- invalid */
+ if (!list_empty(&common->profiles)) {
+ AA_ERROR("%s: internal error, "
+ "policy '%s' still contains profiles\n",
+ __func__, common->name);
+ BUG();
+ }
+ if (!list_empty(&common->list)) {
+ AA_ERROR("%s: internal error, policy '%s' still on list\n",
+ __func__, common->name);
+ BUG();
+ }
+
+ kfree(common->name);
+}
+
+static struct aa_policy_common *__common_find(struct list_head *head,
+ const char *name)
+{
+ struct aa_policy_common *common;
+
+ list_for_each_entry(common, head, list) {
+ if (!strcmp(common->name, name))
+ return common;
+ }


+ return NULL;
+}
+

+static struct aa_policy_common *__common_find_strn(struct list_head *head,
+ const char *str, int len)
+{
+ struct aa_policy_common *common;
+
+ list_for_each_entry(common, head, list) {
+ if (aa_strneq(common->name, str, len))
+ return common;
+ }
+


+ return NULL;
+}
+

+/*
+ * Routines for AppArmor namespaces
+ */
+
+int aa_alloc_default_namespace(void)
+{
+ struct aa_namespace *ns;
+ ns = alloc_aa_namespace("default");
+ if (!ns)
+ return -ENOMEM;
+
+ default_namespace = aa_get_namespace(ns);
+ write_lock(&ns_list_lock);
+ list_add(&ns->base.list, &ns_list);
+ write_unlock(&ns_list_lock);
+


+ return 0;
+}
+

+void aa_free_default_namespace(void)
+{
+ write_lock(&ns_list_lock);
+ list_del_init(&default_namespace->base.list);
+ aa_put_namespace(default_namespace);
+ write_unlock(&ns_list_lock);
+ aa_put_namespace(default_namespace);
+ default_namespace = NULL;
+}
+
+/**
+ * alloc_aa_namespace - allocate, initialize and return a new namespace
+ * @name: a preallocated name
+ * Returns NULL on failure.
+ */
+struct aa_namespace *alloc_aa_namespace(const char *name)
+{
+ struct aa_namespace *ns;
+
+ ns = kzalloc(sizeof(*ns), GFP_KERNEL);
+ AA_DEBUG("%s(%p)\n", __func__, ns);
+ if (!ns)
+ return NULL;
+
+ if (!common_init(&ns->base, name))
+ goto fail_ns;
+
+ /* null profile is not added to the profile list */
+ ns->unconfined = alloc_aa_profile("unconfined");
+ if (!ns->unconfined)
+ goto fail_unconfined;
+
+ ns->unconfined->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ ns->unconfined->flags = PFLAG_UNCONFINED | PFLAG_IX_ON_NAME_ERROR |
+ PFLAG_IMMUTABLE;
+ ns->unconfined->ns = aa_get_namespace(ns);
+
+ return ns;
+
+fail_unconfined:
+ kfree(ns->base.name);
+fail_ns:
+ kfree(ns);


+ return NULL;
+}
+

+/**
+ * free_aa_namespace_kref - free aa_namespace by kref (see aa_put_namespace)
+ * @kr: kref callback for freeing of a namespace
+ */
+void free_aa_namespace_kref(struct kref *kref)
+{
+ free_aa_namespace(container_of(kref, struct aa_namespace, base.count));
+}
+
+/**
+ * free_aa_namespace - free a profile namespace
+ * @namespace: the namespace to free
+ *
+ * Free a namespace. All references to the namespace must have been put.
+ * If the namespace was referenced by a profile confining a task,
+ */
+void free_aa_namespace(struct aa_namespace *ns)
+{
+ if (!ns)
+ return;
+
+ common_free(&ns->base);
+
+ if (ns->unconfined && ns->unconfined->ns == ns)
+ ns->unconfined->ns = NULL;
+
+ aa_put_profile(ns->unconfined);
+ kzfree(ns);
+}
+
+struct aa_namespace *__aa_find_namespace(struct list_head *head,
+ const char *name)
+{
+ return (struct aa_namespace *)__common_find(head, name);
+}
+
+/**
+ * aa_find_namespace - look up a profile namespace on the namespace list
+ * @name: name of namespace to find
+ *
+ * Returns a pointer to the namespace on the list, or NULL if no namespace
+ * called @name exists.
+ */
+struct aa_namespace *aa_find_namespace(const char *name)
+{
+ struct aa_namespace *ns = NULL;
+
+ read_lock(&ns_list_lock);
+ ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
+ read_unlock(&ns_list_lock);
+
+ return ns;
+}
+
+static struct aa_namespace *__aa_find_namespace_by_strn(struct list_head *head,
+ const char *name,
+ int len)
+{
+ return (struct aa_namespace *)__common_find_strn(head, name, len);
+}
+
+struct aa_namespace *aa_find_namespace_by_strn(const char *name, int len)
+{
+ struct aa_namespace *ns = NULL;
+
+ read_lock(&ns_list_lock);
+ ns = aa_get_namespace(__aa_find_namespace_by_strn(&ns_list, name, len));
+ read_unlock(&ns_list_lock);
+
+ return ns;
+}
+
+/**
+ * aa_prepare_namespace - find an existing or create a new namespace of @name
+ * @name: the namespace to find or add
+ */
+struct aa_namespace *aa_prepare_namespace(const char *name)
+{
+ struct aa_namespace *ns;
+
+ write_lock(&ns_list_lock);
+ if (name)
+ ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
+ else
+ ns = aa_get_namespace(default_namespace);
+ if (!ns) {
+ struct aa_namespace *new_ns;
+ write_unlock(&ns_list_lock);
+ new_ns = alloc_aa_namespace(name);
+ if (!new_ns)
+ return NULL;
+ write_lock(&ns_list_lock);
+ ns = __aa_find_namespace(&ns_list, name);
+ if (!ns) {
+ list_add(&new_ns->base.list, &ns_list);
+ ns = aa_get_namespace(new_ns);
+ } else {
+ /* raced so free the new one */
+ free_aa_namespace(new_ns);
+ aa_get_namespace(ns);
+ }
+ }
+ write_unlock(&ns_list_lock);
+
+ return ns;
+}
+
+/*
+ * requires profile->ns set first, takes profiles refcount
+ * TODO: add accounting
+ */
+void __aa_add_profile(struct aa_policy_common *common,


+ struct aa_profile *profile)
+{

+ list_add(&profile->base.list, &common->profiles);
+ if (!(profile->flags & PFLAG_NO_LIST_REF))
+ aa_get_profile(profile);
+}
+
+void __aa_remove_profile(struct aa_profile *profile,
+ struct aa_profile *replacement)
+{
+ if (replacement)
+ profile->replacedby = aa_get_profile(replacement);
+ else
+ profile->replacedby = ERR_PTR(-EINVAL);
+ list_del_init(&profile->base.list);
+ if (!(profile->flags & PFLAG_NO_LIST_REF))
+ aa_put_profile(profile);
+}
+
+/* TODO: add accounting */
+void __aa_replace_profile(struct aa_profile *profile,
+ struct aa_profile *replacement)
+{
+ if (replacement) {
+ struct aa_policy_common *common;
+
+ if (profile->parent)
+ common = &profile->parent->base;
+ else
+ common = &profile->ns->base;
+
+ __aa_remove_profile(profile, replacement);
+ __aa_add_profile(common, replacement);
+ } else
+ __aa_remove_profile(profile, NULL);
+}
+
+/**
+ * __aa_profile_list_release - remove all profiles on the list and put refs
+ * @head: list of profiles
+ */
+void __aa_profile_list_release(struct list_head *head)
+{
+ struct aa_profile *profile, *tmp;
+ list_for_each_entry_safe(profile, tmp, head, base.list) {
+ __aa_profile_list_release(&profile->base.profiles);
+ __aa_remove_profile(profile, NULL);
+ }
+}
+
+void __aa_remove_namespace(struct aa_namespace *ns)
+{
+ struct aa_profile *unconfined = ns->unconfined;
+ list_del_init(&ns->base.list);
+
+ /*
+ * break the ns, unconfined profile cyclic reference and forward
+ * all new unconfined profiles requests to the default namespace
+ */
+ ns->unconfined = aa_get_profile(default_namespace->unconfined);
+ __aa_profile_list_release(&ns->base.profiles);
+ aa_put_profile(unconfined);
+ aa_put_namespace(ns);
+}
+
+/**
+ * aa_remove_namespace = Remove namespace from the list
+ * @ns: namespace to remove
+ */
+void aa_remove_namespace(struct aa_namespace *ns)
+{
+ write_lock(&ns_list_lock);
+ write_lock(&ns->base.lock);
+ __aa_remove_namespace(ns);
+ write_unlock(&ns->base.lock);
+ write_unlock(&ns_list_lock);
+}
+
+/**
+ * aa_profilelist_release - remove all namespaces and all associated profiles
+ */
+void aa_profile_ns_list_release(void)
+{
+ struct aa_namespace *ns, *tmp;
+
+ /* Remove and release all the profiles on namespace profile lists. */
+ write_lock(&ns_list_lock);
+ list_for_each_entry_safe(ns, tmp, &ns_list, base.list) {
+ write_lock(&ns->base.lock);
+ __aa_remove_namespace(ns);
+ write_unlock(&ns->base.lock);
+ }
+ write_unlock(&ns_list_lock);
+}
+
+static const char *fqname_subname(const char *name)
+{
+ char *split;
+ /* check for namespace which begins with a : and ends with : or \0 */
+ name = strstrip((char *)name);
+ if (*name == ':') {
+ split = aa_strchrnul(name + 1, ':');
+ if (*split == '\0')
+ return NULL;


+ name = strstrip(split + 1);
+ }

+ for (split = strstr(name, "//"); split; split = strstr(name, "//"))
+ name = split + 2;
+
+ return name;
+}
+
+/**
+ * alloc_aa_profile - allocate, initialize and return a new profile
+ * @fqname: name of the profile
+ *
+ * Returns NULL on failure.
+ */
+struct aa_profile *alloc_aa_profile(const char *fqname)
+{


+ struct aa_profile *profile;
+

+ profile = kzalloc(sizeof(*profile), GFP_KERNEL);
+ if (!profile)
+ return NULL;
+
+ if (!common_init(&profile->base, fqname)) {
+ kfree(profile);


+ return NULL;
+ }
+

+ profile->fqname = profile->base.name;
+ profile->base.name =
+ (char *)fqname_subname((const char *)profile->fqname);
+ return profile;
+}
+
+/**
+ * aa_new_null_profile - create a new null-X learning profile
+ * @parent: profile that caused this profile to be created
+ * @hat: true if the null- learning profile is a hat
+ *
+ * Create a null- complain mode profile used in learning mode. The name of
+ * the profile is unique and follows the format of parent//null-sid.
+ *
+ * null profiles are added to the profile list but the list does not
+ * hold a count on them so that they are automatically released when
+ * not in use.
+ */
+struct aa_profile *aa_alloc_null_profile(struct aa_profile *parent, int hat)
+{
+ struct aa_profile *profile = NULL;
+ char *name;
+ u32 sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+
+ name = kmalloc(strlen(parent->fqname) + 2 + 7 + 8, GFP_KERNEL);
+ if (!name)
+ goto fail;
+ sprintf(name, "%s//null-%x", parent->fqname, sid);
+
+ profile = alloc_aa_profile(name);
+ kfree(name);
+ if (!profile)
+ goto fail;
+
+ profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ profile->mode = APPARMOR_COMPLAIN;
+ profile->flags = PFLAG_NULL | PFLAG_NO_LIST_REF;
+ if (hat)
+ profile->flags |= PFLAG_HAT;
+
+ profile->parent = aa_get_profile(parent);
+ profile->ns = aa_get_namespace(parent->ns);
+
+ write_lock(&profile->ns->base.lock);
+ __aa_add_profile(&parent->base, profile);
+ write_unlock(&profile->ns->base.lock);
+
+ return profile;
+
+fail:
+ aa_free_sid(sid);


+ return NULL;
+}
+

+/**
+ * free_aa_profile_kref - free aa_profile by kref (called by aa_put_profile)
+ * @kr: kref callback for freeing of a profile
+ */
+void free_aa_profile_kref(struct kref *kref)
+{
+ struct aa_profile *p = container_of(kref, struct aa_profile,
+ base.count);
+
+ free_aa_profile(p);
+}
+
+/**
+ * free_aa_profile - free a profile
+ * @profile: the profile to free
+ *
+ * Free a profile, its hats and null_profile. All references to the profile,
+ * its hats and null_profile must have been put.
+ *
+ * If the profile was referenced from a task context, free_aa_profile() will
+ * be called from an rcu callback routine, so we must not sleep here.
+ */
+void free_aa_profile(struct aa_profile *profile)
+{
+ AA_DEBUG("%s(%p)\n", __func__, profile);
+
+ if (!profile)
+ return;
+
+ /*
+ * profile can still be on the list if the list doesn't hold a
+ * reference. There is no race as NULL profiles can't be attached
+ */
+ if (!list_empty(&profile->base.list)) {
+ if ((profile->flags & PFLAG_NULL) && profile->ns) {
+ write_lock(&profile->ns->base.lock);
+ list_del_init(&profile->base.list);
+ write_unlock(&profile->ns->base.lock);
+ } else {
+ AA_ERROR("%s: internal error, "
+ "profile '%s' still on ns list\n",
+ __func__, profile->base.name);
+ BUG();
+ }
+ }
+
+ /* profile->name is a substring of fqname */
+ profile->base.name = NULL;
+ common_free(&profile->base);
+
+ BUG_ON(!list_empty(&profile->base.profiles));
+
+ kfree(profile->fqname);
+
+ aa_put_namespace(profile->ns);
+ aa_put_profile(profile->parent);
+
+ aa_free_file_rules(&profile->file);
+ aa_free_cap_rules(&profile->caps);
+ aa_free_net_rules(&profile->net);
+ aa_free_rlimit_rules(&profile->rlimits);
+
+ aa_free_sid(profile->sid);
+ aa_match_free(profile->xmatch);
+
+ if (profile->replacedby && !PTR_ERR(profile->replacedby))
+ aa_put_profile(profile->replacedby);
+
+ kzfree(profile);
+}
+
+/* TODO: profile count accounting - setup in remove */
+
+struct aa_profile *__aa_find_profile(struct list_head *head, const char *name)
+{
+ return (struct aa_profile *)__common_find(head, name);
+}
+
+struct aa_profile *__aa_find_profile_by_strn(struct list_head *head,
+ const char *name, int len)
+{
+ return (struct aa_profile *)__common_find_strn(head, name, len);
+}
+
+/**
+ * aa_find_child - find a profile by @name in @parent
+ * @parent: profile to search
+ * @name: profile name to search for
+ *
+ * Returns a ref counted profile or NULL if not found
+ */
+struct aa_profile *aa_find_child(struct aa_profile *parent, const char *name)
+{


+ struct aa_profile *profile;
+

+ read_lock(&parent->ns->base.lock);
+ profile = aa_get_profile(__aa_find_profile(&parent->base.profiles,
+ name));
+ read_unlock(&parent->ns->base.lock);
+
+ return profile;
+}
+
+struct aa_policy_common *__aa_find_parent_by_fqname(struct aa_namespace *ns,
+ const char *fqname)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *profile = NULL;
+ char *split;
+
+ common = &ns->base;
+
+ for (split = strstr(fqname, "//"); split;) {
+ profile = __aa_find_profile_by_strn(&common->profiles, fqname,
+ split - fqname);
+ if (!profile)
+ return NULL;
+ common = &profile->base;
+ fqname = split + 2;
+ split = strstr(fqname, "//");
+ }
+ if (!profile)
+ return &ns->base;
+ return &profile->base;
+}
+
+struct aa_profile *__aa_find_profile_by_fqname(struct aa_namespace *ns,
+ const char *fqname)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *profile = NULL;
+ char *split;
+
+ common = &ns->base;
+ for (split = strstr(fqname, "//"); split;) {
+ profile = __aa_find_profile_by_strn(&common->profiles, fqname,
+ split - fqname);
+ if (!profile)
+ return NULL;
+
+ common = &profile->base;
+ fqname = split + 2;
+ split = strstr(fqname, "//");
+ }
+
+ profile = __aa_find_profile(&common->profiles, fqname);
+
+ return profile;
+}
+
+/**
+ * aa_find_profile_by_name - find a profile by its full or partial name
+ * @ns: the namespace to start from
+ * @fqname: name to do lookup on. Does not contain namespace prefix
+ */
+struct aa_profile *aa_find_profile_by_fqname(struct aa_namespace *ns,
+ const char *fqname)
+{


+ struct aa_profile *profile;
+

+ read_lock(&ns->base.lock);
+ profile = aa_get_profile(__aa_find_profile_by_fqname(ns, fqname));
+ read_unlock(&ns->base.lock);
+ return profile;
+}
+
+/**
+ * aa_profile_newest - find the newest version of @profile
+ * @profile: the profile to check for newer versions of
+ *
+ * Find the newest version of @profile, if @profile is the newest version
+ * return @profile. If @profile has been removed return NULL.
+ *
+ * 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.
+ */
+struct aa_profile *aa_profile_newest(struct aa_profile *profile)
+{
+ if (unlikely(profile && profile->replacedby)) {
+ for (; profile->replacedby; profile = profile->replacedby) {
+ if (IS_ERR(profile->replacedby)) {
+ /* profile has been removed */
+ profile = NULL;
+ break;
+ }
+ }
+ }
+
+ return profile;
+}
diff --git a/security/apparmor/sid.c b/security/apparmor/sid.c
new file mode 100644
index 0000000..e20843f
--- /dev/null
+++ b/security/apparmor/sid.c
@@ -0,0 +1,108 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor security identifier (sid) manipulation fns
+ *


+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *

+ *
+ * AppArmor allocates a unique sid for every profile loaded. If a profile
+ * is replaced it receive the sid of the profile it is replacing. Each sid
+ * is a u32 with the lower u16 being sids of system profiles and the
+ * upper u16 being user profile sids.
+ *
+ * The sid value of 0 is invalid for system sids and is used to indicate
+ * unconfined for user sids.
+ *
+ * A compound sid is a pair of user and system sids that is used to identify
+ * both profiles confining a task.
+ *
+ * Both system and user sids are globally unique with all users pulling
+ * from the same sid pool. User sid allocation is limited by the
+ * user controls, that can limit how many profiles are loaded by a user.
+ */
+
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+
+#include "include/sid.h"
+
+/* global counter from which sids are allocated */
+static u16 global_sys_sid;
+static u16 global_usr_sid;
+static DEFINE_SPINLOCK(sid_lock);
+
+/* TODO FIXME: add sid to profile mapping, and sid recycling */
+
+/**
+ * aa_alloc_sid - allocate a new sid for a profile
+ * @is_usr: true if the new sid is a user based sid
+ */
+u32 aa_alloc_sid(int is_usr)
+{
+ u32 sid;
+
+ /*
+ * TODO FIXME: sid recycling - part of profile mapping table
+ */
+ spin_lock(&sid_lock);
+ if (is_usr)
+ sid = (++global_usr_sid) << 16;
+ else
+ sid = ++global_sys_sid;
+ spin_unlock(&sid_lock);
+ return sid;
+}
+
+/**
+ * aa_free_sid - free a sid
+ * @sid: sid to free
+ */
+void aa_free_sid(u32 sid)
+{
+ ; /* NOP ATM */
+}
+
+/**
+ * aa_add_sid_profile - associate a profile to a sid for sid -> profile lookup
+ * @sid: sid of te profile
+ * @profile: profile to associate
+ *
+ * return 0 or error
+ */
+int aa_add_sid_profile(u32 sid, struct aa_profile *profile)
+{
+ /* NOP ATM */


+ return 0;
+}
+
+/**

+ * aa_replace_sid_profile - replace the profile associated with a sid
+ * @sid: sid to associate a new profile with
+ * @profile: profile to associate with side
+ *
+ * return 0 or error
+ */
+int aa_replace_sid_profile(u32 sid, struct aa_profile *profile)
+{
+ /* NOP ATM */


+ return 0;
+}
+
+/**

+ * aa_get_sid_profile - get the profile associated with the sid
+ * @sid: sid to lookup
+ *
+ * returns - the profile, or NULL for unconfined user.
+ * - if there is an error -ENOENT, -EINVAL
+ */
+struct aa_profile *aa_get_sid_profile(u32 sid)
+{
+ return ERR_PTR(-EINVAL);
+}
--
1.6.3.3

Message has been deleted
Message has been deleted

John Johansen

unread,
Nov 3, 2009, 6:50:34 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
ipc:
AppArmor ipc is currently limited to mediation done by file mediation
and basic ptrace tests. Improved mediation is a wip.

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

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

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

More advanced network rules are a wip.

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

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

set capability sys_admin,

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

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

The current implementation does not set the task capabilities masks.

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/apparmor/capability.c | 121 +++++++++++++++++++++++++++
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 | 105 +++++++++++++++++++++++
security/apparmor/net.c | 144 ++++++++++++++++++++++++++++++++
security/apparmor/resource.c | 103 +++++++++++++++++++++++
8 files changed, 631 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..78c38f5
--- /dev/null
+++ b/security/apparmor/capability.c
@@ -0,0 +1,121 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor capability mediation functions


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.

+ */
+
+#include <linux/capability.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>


+
+#include "include/apparmor.h"
+#include "include/capability.h"

+#include "include/context.h"
+#include "include/policy.h"
+#include "include/audit.h"
+
+/*
+ * Table of capability names: we generate it from capabilities.h.
+ */
+#include "capability_names.h"
+
+struct audit_cache {
+ struct task_struct *task;
+ kernel_cap_t caps;
+};
+
+static DEFINE_PER_CPU(struct audit_cache, audit_cache);
+
+struct aa_audit_caps {
+ struct aa_audit base;
+
+ int cap;
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_caps *sa = va;
+
+ audit_log_format(ab, " name=");
+ audit_log_untrustedstring(ab, capability_names[sa->cap]);
+}
+
+static int aa_audit_caps(struct aa_profile *profile, struct aa_audit_caps *sa)
+{
+ struct audit_cache *ent;
+ int type = AUDIT_APPARMOR_AUTO;
+
+ if (likely(!sa->base.error)) {
+ /* test if auditing is being forced */
+ if (likely((PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !cap_raised(profile->caps.audit, sa->cap)))
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else if (PROFILE_KILL(profile) ||
+ cap_raised(profile->caps.kill, sa->cap)) {
+ type = AUDIT_APPARMOR_KILL;
+ } else if (cap_raised(profile->caps.quiet, sa->cap) &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) {
+ /* quiet auditing */
+ return sa->base.error;
+ }
+
+ /* Do simple duplicate message elimination */
+ ent = &get_cpu_var(audit_cache);
+ if (sa->base.task == ent->task && cap_raised(ent->caps, sa->cap)) {
+ if (PROFILE_COMPLAIN(profile))
+ return 0;
+ return sa->base.error;
+ } else {
+ ent->task = sa->base.task;
+ cap_raise(ent->caps, sa->cap);
+ }
+ put_cpu_var(audit_cache);
+
+ return aa_audit(type, profile, &sa->base, audit_cb);
+}
+
+int aa_profile_capable(struct aa_profile *profile, int cap)
+{
+ return cap_raised(profile->caps.allowed, cap) ? 0 : -EPERM;
+}
+
+/**
+ * aa_capable - test permission to use capability
+ * @task: task doing capability test against
+ * @profile: profile confining @task
+ * @cap: capability to be tested
+ * @audit: whether an audit record should be generated
+ *
+ * Look up capability in profile capability set.
+ * Returns 0 on success, or else an error code.
+ */
+int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
+ int audit)
+{
+ int error = aa_profile_capable(profile, cap);
+ struct aa_audit_caps sa = {
+ .base.operation = "capable",
+ .base.task = task,
+ .base.gfp_mask = GFP_ATOMIC,
+ .base.error = error,
+ .cap = cap,
+ };
+
+ if (!audit) {
+ if (PROFILE_COMPLAIN(profile))
+ return 0;


+ return error;
+ }
+

+ return aa_audit_caps(profile, &sa);
+}
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
new file mode 100644
index 0000000..1343ee9
--- /dev/null
+++ b/security/apparmor/include/capability.h


@@ -0,0 +1,45 @@
+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor capability mediation definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_CAPABILITY_H
+#define __AA_CAPABILITY_H
+
+#include <linux/sched.h>
+
+struct aa_profile;
+
+/* aa_caps - confinement data for capabilities
+ * @set_caps: capabilities that are being set
+ * @capabilities: capabilities mask
+ * @audit_caps: caps that are to be audited
+ * @quiet_caps: caps that should not be audited
+ */
+struct aa_caps {
+ kernel_cap_t set;
+ kernel_cap_t allowed;
+ kernel_cap_t audit;
+ kernel_cap_t quiet;
+ kernel_cap_t kill;
+};
+
+int aa_profile_capable(struct aa_profile *profile, int cap);
+int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
+ int audit);
+
+static inline void aa_free_cap_rules(struct aa_caps *caps)
+{
+ /* NOP */
+}
+
+#endif /* __AA_CAPBILITY_H */
diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h
new file mode 100644
index 0000000..4fe96d3
--- /dev/null
+++ b/security/apparmor/include/ipc.h
@@ -0,0 +1,28 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor ipc mediation function definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_IPC_H
+#define __AA_IPC_H
+
+#include <linux/sched.h>
+
+struct aa_profile;
+
+int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
+ struct aa_profile *tracee, unsigned int mode);
+
+int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
+ unsigned int mode);
+
+#endif /* __AA_IPC_H */
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
new file mode 100644
index 0000000..a3f6d05
--- /dev/null
+++ b/security/apparmor/include/net.h
@@ -0,0 +1,40 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor network mediation definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_NET_H
+#define __AA_NET_H
+
+#include <net/sock.h>
+
+/* struct aa_net - network confinement data
+ * @allowed: basic network families permissions
+ * @audit_network: which network permissions to force audit
+ * @quiet_network: which network permissions to quiet rejects
+ */
+struct aa_net {
+ u16 allowed[AF_MAX];
+ u16 audit[AF_MAX];
+ u16 quiet[AF_MAX];
+};
+
+extern int aa_net_perm(struct aa_profile *profile, char *operation,
+ int family, int type, int protocol);
+extern int aa_revalidate_sk(struct sock *sk, char *operation);
+
+static inline void aa_free_net_rules(struct aa_net *new)
+{
+ /* NOP */
+}
+
+#endif /* __AA_NET_H */
diff --git a/security/apparmor/include/resource.h b/security/apparmor/include/resource.h
new file mode 100644
index 0000000..4281041
--- /dev/null
+++ b/security/apparmor/include/resource.h


@@ -0,0 +1,45 @@
+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor resource limits function defintions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_RESOURCE_H
+#define __AA_RESOURCE_H
+
+#include <linux/resource.h>
+#include <linux/sched.h>
+
+struct aa_profile;
+
+/* struct aa_rlimit - rlimits settings for the profile
+ * @mask: which hard limits to set
+ * @limits: rlimit values that override task limits
+ *
+ * AppArmor rlimits are used to set confined task rlimits. Only the
+ * limits specified in @mask will be controlled by apparmor.
+ */
+struct aa_rlimit {
+ unsigned int mask;
+ struct rlimit limits[RLIM_NLIMITS];
+};
+
+int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim);
+
+void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
+
+static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
+{
+ /* NOP */
+}
+
+#endif /* __AA_RESOURCE_H */
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c
new file mode 100644
index 0000000..2d5c8ef
--- /dev/null
+++ b/security/apparmor/ipc.c
@@ -0,0 +1,105 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor ipc mediation


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/gfp.h>
+#include <linux/ptrace.h>
+
+#include "include/audit.h"
+#include "include/capability.h"


+#include "include/context.h"
+#include "include/policy.h"
+

+struct aa_audit_ptrace {
+ struct aa_audit base;
+
+ pid_t tracer, tracee;
+};
+
+/* call back to audit ptrace fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_ptrace *sa = va;
+ audit_log_format(ab, " tracer=%d tracee=%d", sa->tracer, sa->tracee);
+}
+
+static int aa_audit_ptrace(struct aa_profile *profile,
+ struct aa_audit_ptrace *sa)
+{
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
+ audit_cb);
+}
+
+int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
+ struct aa_profile *tracee, unsigned int mode)
+{
+ /* TODO: currently only based on capability, not extended ptrace
+ * rules,
+ * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
+ */
+
+ if (!tracer || tracer == tracee)
+ return 0;
+ /* log this capability request */
+ return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1);
+}
+
+int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,
+ unsigned int mode)
+{
+ /*
+ * tracer can ptrace tracee when
+ * - tracer is unconfined ||
+ * - tracer & tracee are in the same namespace &&
+ * - tracer is in complain mode
+ * - tracer has rules allowing it to trace tracee currently this is:
+ * - confined by the same profile ||
+ * - tracer profile has CAP_SYS_PTRACE
+ */
+
+ struct aa_profile *tracer_p;
+ const struct cred *cred = aa_get_task_policy(tracer, &tracer_p);


+ int error = 0;
+

+ if (tracer_p) {
+ struct aa_audit_ptrace sa = {
+ .base.operation = "ptrace",
+ .base.gfp_mask = GFP_ATOMIC,
+ .tracer = tracer->pid,
+ .tracee = tracee->pid,
+ };
+ /* FIXME: different namespace restriction can be lifted
+ * if, namespace are matched to AppArmor namespaces
+ */
+ if (tracer->nsproxy != tracee->nsproxy) {
+ sa.base.info = "different namespaces";
+ sa.base.error = -EPERM;
+ aa_audit(AUDIT_APPARMOR_DENIED, tracer_p, &sa.base,
+ audit_cb);
+ } else {
+ struct aa_profile *tracee_p;
+ struct cred *lcred = aa_get_task_policy(tracee,
+ &tracee_p);
+
+ sa.base.error = aa_may_ptrace(tracer, tracer_p,
+ tracee_p, mode);
+ sa.base.error = aa_audit_ptrace(tracer_p, &sa);
+
+ put_cred(lcred);
+ }
+ error = sa.base.error;
+ }
+ put_cred(cred);
+
+ return error;
+}
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..f200d54
--- /dev/null
+++ b/security/apparmor/net.c
@@ -0,0 +1,144 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor network mediation


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/net.h"
+#include "include/policy.h"
+
+#include "af_names.h"
+
+static const char *sock_type_names[] = {
+ "unknown(0)",
+ "stream",
+ "dgram",
+ "raw",
+ "rdm",
+ "seqpacket",
+ "dccp",
+ "unknown(7)",
+ "unknown(8)",
+ "unknown(9)",
+ "packet",
+};
+
+struct aa_audit_net {
+ struct aa_audit base;
+
+ int family, type, protocol;
+
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_net *sa = va;
+
+ if (sa->family || sa->type) {
+ if (address_family_names[sa->family])
+ audit_log_format(ab, " family=\"%s\"",
+ address_family_names[sa->family]);
+ else
+ audit_log_format(ab, " family=\"unknown(%d)\"",
+ sa->family);
+
+ if (sock_type_names[sa->type])
+ audit_log_format(ab, " sock_type=\"%s\"",
+ sock_type_names[sa->type]);
+ else
+ audit_log_format(ab, " sock_type=\"unknown(%d)\"",
+ sa->type);
+
+ audit_log_format(ab, " protocol=%d", sa->protocol);
+ }
+
+}
+
+static int aa_audit_net(struct aa_profile *profile, struct aa_audit_net *sa)
+{
+ int type = AUDIT_APPARMOR_AUTO;
+
+ if (likely(!sa->base.error)) {
+ u16 audit_mask = profile->net.audit[sa->family];
+ if (likely((PROFILE_AUDIT_MODE(profile) != AUDIT_ALL) &&
+ !(1 << sa->type & audit_mask)))
+ return 0;
+ type = AUDIT_APPARMOR_AUDIT;
+ } else {
+ u16 quiet_mask = profile->net.quiet[sa->family];
+ u16 kill_mask = 0;
+ u16 denied = (1 << sa->type) & ~quiet_mask;
+
+ if (denied & kill_mask)
+ type = AUDIT_APPARMOR_KILL;
+
+ if ((denied & quiet_mask) &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_NOQUIET &&
+ PROFILE_AUDIT_MODE(profile) != AUDIT_ALL)
+ return PROFILE_COMPLAIN(profile) ? 0 : sa->base.error;
+ }
+
+ return aa_audit(type, profile, (struct aa_audit *)sa, audit_cb);
+}
+
+int aa_net_perm(struct aa_profile *profile, char *operation,
+ int family, int type, int protocol)
+{
+ u16 family_mask;
+ struct aa_audit_net sa = {
+ .base.operation = operation,
+ .base.gfp_mask = GFP_KERNEL,
+ .family = family,
+ .type = type,
+ .protocol = protocol,
+ };
+
+ if ((family < 0) || (family >= AF_MAX))
+ return -EINVAL;
+
+ if ((type < 0) || (type >= SOCK_MAX))
+ return -EINVAL;
+
+ /* unix domain and netlink sockets are handled by ipc */
+ if (family == AF_UNIX || family == AF_NETLINK)
+ return 0;
+
+ family_mask = profile->net.allowed[family];
+
+ sa.base.error = (family_mask & (1 << type)) ? 0 : -EACCES;
+
+ return aa_audit_net(profile, &sa);
+}
+
+int aa_revalidate_sk(struct sock *sk, char *operation)


+{
+ struct aa_profile *profile;

+ struct cred *cred;


+ int error = 0;
+

+ /* aa_revalidate_sk should not be called from interrupt context
+ * don't mediate these calls as they are not task related
+ */
+ if (in_interrupt())
+ return 0;
+
+ cred = aa_get_task_policy(current, &profile);
+ if (profile)
+ error = aa_net_perm(profile, operation,
+ sk->sk_family, sk->sk_type,
+ sk->sk_protocol);
+ put_cred(cred);


+
+ return error;
+}

diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
new file mode 100644
index 0000000..3153784
--- /dev/null
+++ b/security/apparmor/resource.c
@@ -0,0 +1,103 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor resource mediation and attachment


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/audit.h>
+
+#include "include/audit.h"
+#include "include/resource.h"
+#include "include/policy.h"
+
+struct aa_audit_resource {
+ struct aa_audit base;
+
+ int rlimit;
+};
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_resource *sa = va;
+
+ if (sa->rlimit)
+ audit_log_format(ab, " rlimit=%d", sa->rlimit - 1);
+}
+
+static int aa_audit_resource(struct aa_profile *profile,
+ struct aa_audit_resource *sa)
+{
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
+ audit_cb);
+}
+
+/**
+ * aa_task_setrlimit - test permission to set an rlimit
+ * @profile - profile confining the task
+ * @resource - the resource being set
+ * @new_rlim - the new resource limit
+ *
+ * Control raising the processes hard limit.
+ */
+int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,
+ struct rlimit *new_rlim)
+{
+ struct aa_audit_resource sa = {
+ .base.operation = "setrlimit",
+ .base.gfp_mask = GFP_KERNEL,
+ .rlimit = resource + 1,
+ };
+ int error = 0;
+
+ if (profile->rlimits.mask & (1 << resource) &&
+ new_rlim->rlim_max > profile->rlimits.limits[resource].rlim_max) {
+ sa.base.error = -EACCES;
+
+ error = aa_audit_resource(profile, &sa);
+ }


+
+ return error;
+}
+

+void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new)
+{
+ unsigned int mask = 0;
+ struct rlimit *rlim, *initrlim;
+ int i;
+
+ /* for any rlimits the profile controlled reset the soft limit
+ * to the less of the tasks hard limit and the init tasks soft limit
+ */
+ if (old && old->rlimits.mask) {
+ for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
+ if (old->rlimits.mask & mask) {
+ rlim = current->signal->rlim + i;
+ initrlim = init_task.signal->rlim + i;
+ rlim->rlim_cur = min(rlim->rlim_max,
+ initrlim->rlim_cur);
+ }
+ }
+ }
+
+ /* set any new hard limits as dictated by the new profile */
+ if (!(new && new->rlimits.mask))
+ return;
+ for (i = 0, mask = 1; i < RLIM_NLIMITS; i++, mask <<= 1) {
+ if (!(new->rlimits.mask & mask))
+ continue;
+
+ rlim = current->signal->rlim + i;
+ rlim->rlim_max = min(rlim->rlim_max,
+ new->rlimits.limits[i].rlim_max);
+ /* soft limit should not exceed hard limit */
+ rlim->rlim_cur = min(rlim->rlim_cur, rlim->rlim_max);
+ }
+}
--
1.6.3.3

John Johansen

unread,
Nov 3, 2009, 6:50:42 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
AppArmor hooks to interface with the LSM, and module parameters and
initialization.

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/apparmor/lsm.c | 1029 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1029 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..84ef552
--- /dev/null
+++ b/security/apparmor/lsm.c
@@ -0,0 +1,1029 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor LSM hooks.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/security.h>
+#include <linux/moduleparam.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/ptrace.h>
+#include <linux/ctype.h>
+#include <linux/sysctl.h>
+#include <linux/audit.h>
+#include <net/sock.h>
+
+#include "include/apparmor.h"
+#include "include/apparmorfs.h"


+#include "include/audit.h"
+#include "include/capability.h"
+#include "include/context.h"

+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/net.h"
+#include "include/path.h"
+#include "include/policy.h"
+#include "include/procattr.h"


+
+/* Flag indicating whether initialization completed */

+int apparmor_initialized;
+
+/*
+ * LSM hook functions
+ */
+
+/*
+ * prepare new aa_task_context for modification by prepare_cred block
+ */
+static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{
+ struct aa_task_context *cxt = aa_dup_task_context(old->security, gfp);
+ if (!cxt)
+ return -ENOMEM;
+ new->security = cxt;


+ return 0;
+}
+

+/*
+ * free the associated aa_task_context and put its profiles
+ */
+static void apparmor_cred_free(struct cred *cred)


+{
+ struct aa_task_context *cxt = cred->security;

+ cred->security = NULL;
+ aa_free_task_context(cxt);
+}
+
+static int apparmor_ptrace_access_check(struct task_struct *child,


+ unsigned int mode)
+{

+ return aa_ptrace(current, child, mode);
+}
+
+static int apparmor_ptrace_traceme(struct task_struct *parent)
+{
+ return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+}
+
+/* Derived from security/commoncap.c:cap_capget */
+static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
+ kernel_cap_t *inheritable, kernel_cap_t *permitted)


+{
+ struct aa_profile *profile;

+ const struct cred *cred;
+
+ rcu_read_lock();
+ cred = __task_cred(target);
+ aa_cred_policy(cred, &profile);
+
+ *effective = cred->cap_effective;
+ *inheritable = cred->cap_inheritable;
+ *permitted = cred->cap_permitted;
+
+ if (profile) {
+ *effective = cap_combine(*effective, profile->caps.set);
+ *effective = cap_intersect(*effective, profile->caps.allowed);
+ }
+ rcu_read_unlock();
+


+ return 0;
+}
+

+static int apparmor_capable(struct task_struct *task, const struct cred *cred,
+ int cap, int audit)


+{
+ struct aa_profile *profile;

+ /* cap_capable returns 0 on success, else -EPERM */
+ int error = cap_capable(task, cred, cap, audit);
+
+ aa_cred_policy(cred, &profile);
+ if (profile && (!error || cap_raised(profile->caps.set, cap)))
+ error = aa_capable(task, profile, cap, audit);


+
+ return error;
+}
+

+static int apparmor_sysctl(struct ctl_table *table, int op)


+{
+ int error = 0;

+ struct aa_profile *profile = aa_current_profile_wupd();
+
+ if (profile) {
+ char *buffer, *name;
+ int mask;
+
+ mask = 0;
+ if (op & 4)
+ mask |= MAY_READ;
+ if (op & 2)
+ mask |= MAY_WRITE;
+
+ error = -ENOMEM;
+ buffer = (char *)__get_free_page(GFP_KERNEL);
+ if (!buffer)
+ goto out;
+
+ /*
+ * TODO: convert this over to using a global or per
+ * namespace control instead of a hard coded /proc
+ */
+ name = sysctl_pathname(table, buffer, PAGE_SIZE);
+ if (name && name - buffer >= 5) {
+ struct path_cond cond = { 0, S_IFREG };
+ name -= 5;
+ memcpy(name, "/proc", 5);
+ error = aa_pathstr_perm(profile, "sysctl", name, mask,
+ &cond);
+ }
+ free_page((unsigned long)buffer);
+ }
+
+out:


+ return error;
+}
+

+static int common_perm(const char *op, struct path *path, u16 mask,
+ struct path_cond *cond)


+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ profile = aa_current_profile();
+ if (profile)
+ error = aa_path_perm(profile, op, path, mask, cond);


+
+ return error;
+}
+

+static int common_perm_dentry(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask,
+ struct path_cond *cond)
+{
+ struct path path = { dir->mnt, dentry };
+
+ return common_perm(op, &path, mask, cond);
+}
+
+static int common_perm_rm(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask)
+{
+ struct inode *inode = dentry->d_inode;
+ struct path_cond cond = { };
+
+ if (!dir->mnt || !inode || !mediated_filesystem(inode))
+ return 0;
+
+ cond.uid = inode->i_uid;
+ cond.mode = inode->i_mode;
+
+ return common_perm_dentry(op, dir, dentry, mask, &cond);
+}
+
+static int common_perm_create(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask, umode_t mode)
+{
+ struct path_cond cond = { current_fsuid(), mode };
+
+ if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))
+ return 0;
+
+ return common_perm_dentry(op, dir, dentry, mask, &cond);
+}
+
+static int apparmor_path_unlink(struct path *dir, struct dentry *dentry)
+{
+ return common_perm_rm("unlink", dir, dentry, MAY_WRITE);
+}
+
+static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry,
+ int mode)
+{
+ return common_perm_create("mkdir", dir, dentry, AA_MAY_CREATE, S_IFDIR);
+}
+
+static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry)
+{
+ return common_perm_rm("rmdir", dir, dentry, MAY_WRITE);
+}
+
+static int apparmor_path_mknod(struct path *dir, struct dentry *dentry,
+ int mode, unsigned int dev)
+{
+ return common_perm_create("mknod", dir, dentry, AA_MAY_CREATE, mode);
+}
+
+static int apparmor_path_truncate(struct path *path, loff_t length,
+ unsigned int time_attrs)
+{
+ struct path_cond cond = { path->dentry->d_inode->i_uid,
+ path->dentry->d_inode->i_mode
+ };
+
+ if (!path->mnt || !mediated_filesystem(path->dentry->d_inode))
+ return 0;
+ return common_perm("truncate", path, MAY_WRITE, &cond);
+}
+
+static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
+ const char *old_name)
+{
+ return common_perm_create("symlink_create", dir, dentry, AA_MAY_CREATE,
+ S_IFLNK);
+}
+
+static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry)


+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile)
+ error = aa_path_link(profile, old_dentry, new_dir, new_dentry);


+ return error;
+}
+

+static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry)


+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile) {
+ struct path old_path = { old_dir->mnt, old_dentry };
+ struct path new_path = { new_dir->mnt, new_dentry };
+ struct path_cond cond = { old_dentry->d_inode->i_uid,
+ old_dentry->d_inode->i_mode
+ };
+
+ error = aa_path_perm(profile, "rename_src", &old_path,
+ MAY_READ | MAY_WRITE, &cond);
+ if (!error)
+ error = aa_path_perm(profile, "rename_dest", &new_path,
+ AA_MAY_CREATE | MAY_WRITE, &cond);


+
+ }
+ return error;
+}
+

+static int apparmor_dentry_open(struct file *file, const struct cred *cred)


+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ /* If in exec permission is handled by bprm hooks */
+ if (current->in_execve ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ aa_cred_policy(cred, &profile);
+ if (profile) {
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct path_cond cond = { inode->i_uid, inode->i_mode };
+
+ error = aa_path_perm(profile, "open", &file->f_path,
+ aa_map_file_to_perms(file), &cond);
+ 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)
+{
+ file->f_security = aa_alloc_file_context(GFP_KERNEL);
+ if (!file->f_security)
+ return -ENOMEM;


+ return 0;
+
+}

+
+static void apparmor_file_free_security(struct file *file)
+{
+ struct aa_file_cxt *cxt = file->f_security;
+
+ aa_free_file_context(cxt);
+}
+
+static int apparmor_file_permission(struct file *file, int mask)
+{
+ /*
+ * TODO: cache profiles that have revalidated?
+ */
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = fcxt->profile;


+ int error = 0;
+

+ if (!fprofile || !file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile();
+
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ /*
+ * AppArmor <= 2.4 revalidates files at access time instead
+ * of at exec.
+ */
+ if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
+ error = aa_file_perm(profile, "file_perm", file, mask);
+#endif


+
+ return error;
+}
+

+static int common_file_perm(const char *op, struct file *file, u16 mask)
+{
+ const struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = fcxt->profile;


+ int error = 0;
+

+ if (!fprofile || !file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
+ error = aa_file_perm(profile, op, file, mask);


+
+ return error;
+}
+

+static int apparmor_file_lock(struct file *file, unsigned int cmd)
+{
+ u16 mask = AA_MAY_LOCK;
+
+ if (cmd == F_WRLCK)
+ mask |= MAY_WRITE;
+
+ return common_file_perm("file_lock", file, mask);
+}
+
+static int common_mmap(struct file *file, const char *operation,
+ unsigned long prot, unsigned long flags)
+{
+ struct dentry *dentry;
+ int mask = 0;
+
+ if (!file || !file->f_security)
+ return 0;
+
+ if (prot & PROT_READ)
+ mask |= MAY_READ;
+ /*
+ *Private mappings don't require write perms since they don't
+ * write back to the files
+ */
+ if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE))
+ mask |= MAY_WRITE;
+ if (prot & PROT_EXEC)
+ mask |= AA_EXEC_MMAP;
+
+ dentry = file->f_path.dentry;
+ return common_file_perm(operation, file, mask);
+}
+
+static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags,
+ unsigned long addr, unsigned long addr_only)
+{
+ int rc = 0;
+ struct aa_profile *profile = aa_current_profile_wupd();
+ /*
+ * test before cap_file_mmap. For confined tasks AppArmor will
+ * enforce the mmap value set in the profile or default
+ * to LSM_MMAP_MIN_ADDR
+ */
+ if (profile) {
+ if (profile->flags & PFLAG_MMAP_MIN_ADDR) {
+ if (addr < profile->mmap_min_addr)
+ rc = -EACCES;
+ } else if (addr < CONFIG_LSM_MMAP_MIN_ADDR) {
+ rc = -EACCES;
+ }
+ if (rc) {


+ struct aa_audit sa = {

+ .operation = "file_mmap",
+ .gfp_mask = GFP_KERNEL,
+ .info = "addr < mmap_min_addr",
+ .error = rc,
+ };
+ return aa_audit(AUDIT_APPARMOR_DENIED, profile, &sa,
+ NULL);
+ }
+ }
+ rc = cap_file_mmap(file, reqprot, prot, flags, addr, addr_only);
+ if (rc || addr_only)
+ return rc;
+
+ return common_mmap(file, "file_mmap", prot, flags);
+}
+
+static int apparmor_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+{
+ return common_mmap(vma->vm_file, "file_mprotect", prot,
+ !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
+}
+
+static int apparmor_getprocattr(struct task_struct *task, char *name,
+ char **value)
+{
+ int error = -ENOENT;
+ struct aa_namespace *ns;
+ struct aa_profile *profile, *onexec, *prev;
+ const struct cred *cred = aa_get_task_policy(task, &profile);


+ struct aa_task_context *cxt = cred->security;

+ ns = cxt->sys.profile->ns;
+ onexec = cxt->sys.onexec;
+ prev = cxt->sys.previous;
+
+ /* task must be either querying itself, unconfined or can ptrace */
+ if (current != task && profile && !capable(CAP_SYS_PTRACE)) {
+ error = -EPERM;
+ } else {
+ if (strcmp(name, "current") == 0) {
+ error = aa_getprocattr(ns, profile, value);
+ } else if (strcmp(name, "prev") == 0) {
+ if (prev)
+ error = aa_getprocattr(ns, prev, value);
+ } else if (strcmp(name, "exec") == 0) {
+ if (onexec)
+ error = aa_getprocattr(ns, onexec, value);
+ } else {
+ error = -EINVAL;
+ }
+ }


+
+ put_cred(cred);
+
+ return error;
+}

+
+static int apparmor_setprocattr(struct task_struct *task, char *name,
+ void *value, size_t size)
+{
+ char *command, *args;
+ int error;
+
+ if (size == 0 || size >= PAGE_SIZE)
+ return -EINVAL;
+
+ /* task can only write its own attributes */
+ if (current != task)
+ return -EACCES;
+
+ args = value;
+ args[size] = '\0';
+ args = strstrip(args);
+ command = strsep(&args, " ");
+ if (!args)
+ return -EINVAL;
+ while (isspace(*args))
+ args++;
+ if (!*args)
+ return -EINVAL;
+
+ if (strcmp(name, "current") == 0) {
+ if (strcmp(command, "changehat") == 0) {
+ error = aa_setprocattr_changehat(args, !AA_DO_TEST);
+ } else if (strcmp(command, "permhat") == 0) {
+ error = aa_setprocattr_changehat(args, AA_DO_TEST);
+ } else if (strcmp(command, "changeprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, 0,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, 0,
+ AA_DO_TEST);
+ } else if (strcmp(command, "permipc") == 0) {
+ error = aa_setprocattr_permipc(args);
+ } else {


+ struct aa_audit sa = {

+ .operation = "setprocattr",
+ .gfp_mask = GFP_KERNEL,
+ .info = name,
+ .error = -EINVAL,
+ };
+ return aa_audit(AUDIT_APPARMOR_DENIED, NULL, &sa, NULL);
+ }
+ } else if (strcmp(name, "exec") == 0) {
+ error = aa_setprocattr_changeprofile(strstrip(args), 1,
+ !AA_DO_TEST);
+ } else {
+ /* only support the "current" and "exec" process attributes */
+ return -EINVAL;
+ }
+ if (!error)
+ error = size;


+ return error;
+}
+

+static int apparmor_task_setrlimit(unsigned int resource,


+ struct rlimit *new_rlim)
+{

+ struct aa_profile *profile = aa_current_profile_wupd();


+ int error = 0;
+

+ if (profile)
+ error = aa_task_setrlimit(profile, resource, new_rlim);


+
+ return error;
+}
+

+#ifdef CONFIG_SECURITY_APPARMOR_NETWORK
+static int apparmor_socket_create(int family, int type, int protocol, int kern)


+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ if (kern)
+ return 0;
+
+ profile = aa_current_profile();
+ if (profile)
+ error = aa_net_perm(profile, "socket_create", family,
+ type, protocol);


+ return error;
+}
+

+static int apparmor_socket_post_create(struct socket *sock, int family,
+ int type, int protocol, int kern)
+{
+ struct sock *sk = sock->sk;
+
+ if (kern)
+ return 0;
+
+ return aa_revalidate_sk(sk, "socket_post_create");
+}
+
+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_bind");
+}
+
+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_connect");
+}
+
+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_listen");
+}
+
+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_accept");
+}
+
+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_sendmsg");
+}
+
+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_recvmsg");
+}
+
+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_getsockname");
+}
+
+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_getpeername");
+}
+
+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_getsockopt");
+}
+
+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_setsockopt");
+}
+
+static int apparmor_socket_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+
+ return aa_revalidate_sk(sk, "socket_shutdown");
+}
+#endif
+
+static struct security_operations apparmor_ops = {
+ .name = "apparmor",
+
+ .ptrace_access_check = apparmor_ptrace_access_check,
+ .ptrace_traceme = apparmor_ptrace_traceme,
+ .capget = apparmor_capget,
+ .sysctl = apparmor_sysctl,
+ .capable = apparmor_capable,
+
+ .path_link = apparmor_path_link,
+ .path_unlink = apparmor_path_unlink,
+ .path_symlink = apparmor_path_symlink,
+ .path_mkdir = apparmor_path_mkdir,
+ .path_rmdir = apparmor_path_rmdir,
+ .path_mknod = apparmor_path_mknod,
+ .path_rename = apparmor_path_rename,
+ .path_truncate = apparmor_path_truncate,
+ .dentry_open = apparmor_dentry_open,
+
+ .file_permission = apparmor_file_permission,
+ .file_alloc_security = apparmor_file_alloc_security,
+ .file_free_security = apparmor_file_free_security,
+ .file_mmap = apparmor_file_mmap,
+ .file_mprotect = apparmor_file_mprotect,
+ .file_lock = apparmor_file_lock,
+
+ .getprocattr = apparmor_getprocattr,
+ .setprocattr = apparmor_setprocattr,
+
+#ifdef CONFIG_SECURITY_APPARMOR_NETWORK
+ .socket_create = apparmor_socket_create,
+ .socket_post_create = apparmor_socket_post_create,
+ .socket_bind = apparmor_socket_bind,
+ .socket_connect = apparmor_socket_connect,
+ .socket_listen = apparmor_socket_listen,
+ .socket_accept = apparmor_socket_accept,
+ .socket_sendmsg = apparmor_socket_sendmsg,
+ .socket_recvmsg = apparmor_socket_recvmsg,
+ .socket_getsockname = apparmor_socket_getsockname,
+ .socket_getpeername = apparmor_socket_getpeername,
+ .socket_getsockopt = apparmor_socket_getsockopt,
+ .socket_setsockopt = apparmor_socket_setsockopt,
+ .socket_shutdown = apparmor_socket_shutdown,
+#endif
+
+ .cred_free = apparmor_cred_free,
+ .cred_prepare = apparmor_cred_prepare,
+
+ .bprm_set_creds = apparmor_bprm_set_creds,
+ .bprm_committing_creds = apparmor_bprm_committing_creds,
+ .bprm_committed_creds = apparmor_bprm_committed_creds,
+ .bprm_secureexec = apparmor_bprm_secureexec,
+
+ .task_setrlimit = apparmor_task_setrlimit,
+};
+
+/*
+ * AppArmor sysfs module parameters
+ */
+
+static int param_set_aabool(const char *val, struct kernel_param *kp);
+static int param_get_aabool(char *buffer, struct kernel_param *kp);
+#define param_check_aabool(name, p) __param_check(name, p, int)
+
+static int param_set_aauint(const char *val, struct kernel_param *kp);
+static int param_get_aauint(char *buffer, struct kernel_param *kp);
+#define param_check_aauint(name, p) __param_check(name, p, int)
+
+static int param_set_aalockpolicy(const char *val, struct kernel_param *kp);
+static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp);
+#define param_check_aalockpolicy(name, p) __param_check(name, p, int)
+
+static int param_set_audit(const char *val, struct kernel_param *kp);
+static int param_get_audit(char *buffer, struct kernel_param *kp);
+#define param_check_audit(name, p) __param_check(name, p, int)
+
+static int param_set_mode(const char *val, struct kernel_param *kp);
+static int param_get_mode(char *buffer, struct kernel_param *kp);
+#define param_check_mode(name, p) __param_check(name, p, int)
+
+/* Flag values, also controllable via /sys/module/apparmor/parameters
+ * We define special types as we want to do additional mediation.
+ */
+
+/* AppArmor global enforcement switch - complain, enforce, kill */
+enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
+module_param_call(mode, param_set_mode, param_get_mode,
+ &aa_g_profile_mode, S_IRUSR | S_IWUSR);
+
+/* Debug mode */
+int aa_g_debug;
+module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR);
+
+/* Audit mode */
+enum audit_mode aa_g_audit;
+module_param_call(audit, param_set_audit, param_get_audit,
+ &aa_g_audit, S_IRUSR | S_IWUSR);
+
+/* Determines if audit header is included in audited messages. This
+ * provides more context if the audit daemon is not running
+ */
+int aa_g_audit_header;
+module_param_named(audit_header, aa_g_audit_header, aabool,
+ S_IRUSR | S_IWUSR);
+
+/* lock out loading/removal of policy
+ * TODO: add in at boot loading of policy, which is the only way to
+ * load policy, if lock_policy is set
+ */
+int aa_g_lock_policy;
+module_param_named(lock_policy, aa_g_lock_policy, aalockpolicy,
+ S_IRUSR | S_IWUSR);
+
+/* Syscall logging mode */
+int aa_g_logsyscall;
+module_param_named(logsyscall, aa_g_logsyscall, aabool,
+ S_IRUSR | S_IWUSR);
+
+/* Maximum pathname length before accesses will start getting rejected */
+unsigned int aa_g_path_max = 2 * PATH_MAX;
+module_param_named(path_max, aa_g_path_max, aauint, S_IRUSR | S_IWUSR);
+
+/* Boot time disable flag */
+#ifdef CONFIG_SECURITY_APPARMOR_DISABLE
+#define AA_ENABLED_PERMS 0600
+#else
+#define AA_ENABLED_PERMS 0400
+#endif
+static int param_set_aa_enabled(const char *val, struct kernel_param *kp);
+static unsigned int apparmor_enabled = CONFIG_SECURITY_APPARMOR_BOOTPARAM_VALUE;
+module_param_call(enabled, param_set_aa_enabled, param_get_aauint,
+ &apparmor_enabled, AA_ENABLED_PERMS);
+
+static int __init apparmor_enabled_setup(char *str)
+{
+ unsigned long enabled;
+ int error = strict_strtoul(str, 0, &enabled);
+ if (!error)
+ apparmor_enabled = enabled ? 1 : 0;


+ return 1;
+}
+

+__setup("apparmor=", apparmor_enabled_setup);
+
+static int param_set_aalockpolicy(const char *val, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ if (aa_g_lock_policy)
+ return -EACCES;
+ return param_set_bool(val, kp);
+}
+
+static int param_get_aalockpolicy(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_get_bool(buffer, kp);
+}
+
+static int param_set_aabool(const char *val, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_set_bool(val, kp);
+}
+
+static int param_get_aabool(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_get_bool(buffer, kp);
+}
+
+static int param_set_aauint(const char *val, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_set_uint(val, kp);
+}
+
+static int param_get_aauint(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+ return param_get_uint(buffer, kp);
+}
+
+/* allow run time disabling of apparmor */
+static int param_set_aa_enabled(const char *val, struct kernel_param *kp)
+{
+ unsigned long l;
+
+ if (!apparmor_initialized) {
+ apparmor_enabled = 0;


+ return 0;
+ }
+

+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+
+ if (strict_strtoul(val, 0, &l) || l != 0)
+ return -EINVAL;
+
+ apparmor_enabled = 0;
+ apparmor_disable();


+ return 0;
+}
+

+static int param_get_audit(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
+}
+
+static int param_set_audit(const char *val, struct kernel_param *kp)
+{
+ int i;
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+
+ for (i = 0; i < AUDIT_MAX_INDEX; i++) {
+ if (strcmp(val, audit_mode_names[i]) == 0) {
+ aa_g_audit = i;


+ return 0;
+ }
+ }

+


+ return -EINVAL;
+}
+

+static int param_get_mode(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]);
+}
+
+static int param_set_mode(const char *val, struct kernel_param *kp)
+{
+ int i;
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)
+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+
+ for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) {
+ if (strcmp(val, profile_mode_names[i]) == 0) {
+ aa_g_profile_mode = i;


+ return 0;
+ }
+ }

+


+ return -EINVAL;
+}
+

+/*
+ * AppArmor init functions
+ */
+static int set_init_cxt(void)
+{
+ struct cred *cred = (struct cred *)current->real_cred;


+ struct aa_task_context *cxt;
+

+ cxt = aa_alloc_task_context(GFP_KERNEL);
+ if (!cxt)
+ return -ENOMEM;
+
+ cxt->sys.profile = aa_get_profile(default_namespace->unconfined);
+ cred->security = cxt;
+


+ return 0;
+}
+

+static int __init apparmor_init(void)
+{
+ int error;
+
+ if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) {
+ aa_info_message("AppArmor disabled by boot time parameter\n");
+ apparmor_enabled = 0;


+ return 0;
+ }
+

+ error = aa_alloc_default_namespace();
+ if (error) {
+ AA_ERROR("Unable to allocate default profile namespace\n");
+ goto alloc_out;
+ }
+
+ error = set_init_cxt();
+ if (error) {
+ AA_ERROR("Failed to set context on init task\n");
+ goto alloc_out;
+ }
+
+ error = register_security(&apparmor_ops);
+ if (error) {
+ AA_ERROR("Unable to register AppArmor\n");
+ goto register_security_out;
+ }
+
+ /* Report that AppArmor successfully initialized */
+ apparmor_initialized = 1;
+ if (aa_g_profile_mode == APPARMOR_COMPLAIN)
+ aa_info_message("AppArmor initialized: complain mode enabled");
+ else if (aa_g_profile_mode == APPARMOR_KILL)
+ aa_info_message("AppArmor initialized: kill mode enabled");
+ else
+ aa_info_message("AppArmor initialized");


+
+ return error;
+

+register_security_out:
+ aa_free_default_namespace();
+
+alloc_out:
+ aa_destroy_aafs();
+
+ apparmor_enabled = 0;


+ return error;
+
+}
+

+security_initcall(apparmor_init);
+
+void apparmor_disable(void)
+{
+ /* Remove and release all the profiles on the profile list. */
+ aa_profile_ns_list_release();
+
+ /* FIXME: cleanup profiles references on files */
+ aa_free_default_namespace();
+
+ aa_destroy_aafs();
+ apparmor_initialized = 0;
+
+ aa_info_message("AppArmor protection disabled");
+}
--
1.6.3.3

Message has been deleted

John Johansen

unread,
Nov 3, 2009, 6:51:01 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
Kconfig and Makefiles to enable configuration and building of AppArmor.

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/Kconfig | 1 +
security/Makefile | 2 +
security/apparmor/.gitignore | 5 +++
security/apparmor/Kconfig | 62 ++++++++++++++++++++++++++++++++++++++++++
security/apparmor/Makefile | 25 +++++++++++++++++
5 files changed, 95 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/.gitignore
create mode 100644 security/apparmor/Kconfig
create mode 100644 security/apparmor/Makefile

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

source security/integrity/ima/Kconfig

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

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

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

Message has been deleted

John Johansen

unread,
Nov 3, 2009, 6:52:35 PM11/3/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
A basic dfa matching engine based off the dfa engine in the Dragon
Book. It uses simple row compression with a check field.

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

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

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

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/apparmor/include/match.h | 104 +++++++++++++
security/apparmor/match.c | 299 +++++++++++++++++++++++++++++++++++++
2 files changed, 403 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/match.h
create mode 100644 security/apparmor/match.c

diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
new file mode 100644
index 0000000..bd5015d
--- /dev/null
+++ b/security/apparmor/include/match.h
@@ -0,0 +1,104 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor policy dfa matching engine definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_MATCH_H
+#define __AA_MATCH_H
+
+#define DFA_NOMATCH 0
+#define DFA_START 1
+
+#define DFA_VALID_PERM_MASK 0xffffffff
+#define DFA_VALID_PERM2_MASK 0xffffffff
+
+/**
+ * The format used for transition tables is based on the GNU flex table
+ * file format (--tables-file option; see Table File Format in the flex
+ * info pages and the flex sources for documentation). The magic number
+ * used in the header is 0x1B5E783D insted of 0xF13C57B1 though, because
+ * the YY_ID_CHK (check) and YY_ID_DEF (default) tables are used
+ * slightly differently (see the apparmor-parser package).
+ */
+
+#define YYTH_MAGIC 0x1B5E783D
+
+struct table_set_header {
+ u32 th_magic; /* YYTH_MAGIC */
+ u32 th_hsize;
+ u32 th_ssize;
+ u16 th_flags;
+ char th_version[];
+};
+
+#define YYTD_ID_ACCEPT 1
+#define YYTD_ID_BASE 2
+#define YYTD_ID_CHK 3
+#define YYTD_ID_DEF 4
+#define YYTD_ID_EC 5
+#define YYTD_ID_META 6
+#define YYTD_ID_ACCEPT2 7
+#define YYTD_ID_NXT 8
+
+#define YYTD_DATA8 1
+#define YYTD_DATA16 2
+#define YYTD_DATA32 4
+
+struct table_header {
+ u16 td_id;
+ u16 td_flags;
+ u32 td_hilen;
+ u32 td_lolen;
+ char td_data[];
+};
+
+#define DEFAULT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_DEF - 1]->td_data))
+#define BASE_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_BASE - 1]->td_data))
+#define NEXT_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_NXT - 1]->td_data))
+#define CHECK_TABLE(DFA) ((u16 *)((DFA)->tables[YYTD_ID_CHK - 1]->td_data))
+#define EQUIV_TABLE(DFA) ((u8 *)((DFA)->tables[YYTD_ID_EC - 1]->td_data))
+#define ACCEPT_TABLE(DFA) ((u32 *)((DFA)->tables[YYTD_ID_ACCEPT - 1]->td_data))
+#define ACCEPT_TABLE2(DFA) ((u32 *)\
+ ((DFA)->tables[YYTD_ID_ACCEPT2 - 1]->td_data))
+
+struct aa_dfa {
+ struct table_header *tables[YYTD_ID_NXT];
+};
+
+#define byte_to_byte(X) (X)
+
+#define UNPACK_ARRAY(TABLE, BLOB, LEN, TYPE, NTOHX) \
+ do { \
+ typeof(LEN) __i; \
+ TYPE *__t = (TYPE *) TABLE; \
+ TYPE *__b = (TYPE *) BLOB; \
+ for (__i = 0; __i < LEN; __i++) { \
+ __t[__i] = NTOHX(__b[__i]); \
+ } \
+ } while (0)
+
+static inline size_t table_size(size_t len, size_t el_size)
+{
+ return ALIGN(sizeof(struct table_header) + len * el_size, 8);
+}
+
+struct aa_dfa *aa_match_alloc(void);
+void aa_match_free(struct aa_dfa *dfa);
+int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size);
+int verify_dfa(struct aa_dfa *dfa);
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len);
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str);
+unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start);
+
+#endif /* __AA_MATCH_H */
diff --git a/security/apparmor/match.c b/security/apparmor/match.c
new file mode 100644
index 0000000..d8f2eb1
--- /dev/null
+++ b/security/apparmor/match.c
@@ -0,0 +1,299 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor dfa based regular expression matching engine


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include "include/apparmor.h"
+#include "include/match.h"
+#include "include/file.h"
+
+static void free_table(struct table_header *table)
+{
+ if (is_vmalloc_addr(table))
+ vfree(table);
+ else
+ kfree(table);
+}
+
+static struct table_header *unpack_table(void *blob, size_t bsize)
+{
+ struct table_header *table = NULL;
+ struct table_header th;
+ size_t tsize;
+
+ if (bsize < sizeof(struct table_header))
+ goto out;
+
+ th.td_id = be16_to_cpu(*(u16 *) (blob));
+ th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
+ th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
+ blob += sizeof(struct table_header);
+
+ if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
+ th.td_flags == YYTD_DATA8))
+ goto out;
+
+ tsize = table_size(th.td_lolen, th.td_flags);
+ if (bsize < tsize)
+ goto out;
+
+ table = kmalloc(tsize, GFP_KERNEL);
+ if (!table)
+ table = vmalloc(tsize);
+ if (table) {
+ *table = th;
+ if (th.td_flags == YYTD_DATA8)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u8, byte_to_byte);
+ else if (th.td_flags == YYTD_DATA16)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u16, be16_to_cpu);
+ else
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u32, be32_to_cpu);
+ }
+
+out:
+ return table;
+}
+
+int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
+{
+ int hsize, i;
+ int error = -ENOMEM;
+
+ /* get dfa table set header */
+ if (size < sizeof(struct table_set_header))
+ goto fail;
+
+ if (ntohl(*(u32 *) blob) != YYTH_MAGIC)
+ goto fail;
+
+ hsize = ntohl(*(u32 *) (blob + 4));
+ if (size < hsize)
+ goto fail;
+
+ blob += hsize;
+ size -= hsize;
+
+ error = -EPROTO;
+ while (size > 0) {
+ struct table_header *table;
+ table = unpack_table(blob, size);
+ if (!table)
+ goto fail;
+
+ switch (table->td_id) {
+ case YYTD_ID_ACCEPT:
+ case YYTD_ID_ACCEPT2:
+ case YYTD_ID_BASE:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA32)
+ goto fail;
+ break;
+ case YYTD_ID_DEF:
+ case YYTD_ID_NXT:
+ case YYTD_ID_CHK:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA16)
+ goto fail;
+ break;
+ case YYTD_ID_EC:
+ dfa->tables[table->td_id - 1] = table;
+ if (table->td_flags != YYTD_DATA8)
+ goto fail;
+ break;
+ default:
+ free_table(table);


+ goto fail;
+ }
+

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


+
+ return 0;
+

+fail:
+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
+ free_table(dfa->tables[i]);
+ dfa->tables[i] = NULL;


+ }
+ return error;
+}
+

+/**
+ * verify_dfa - verify that all the transitions and states in the dfa tables
+ * are in bounds.
+ * @dfa: dfa to test
+ *
+ * assumes dfa has gone through the verification done by unpacking
+ */
+int verify_dfa(struct aa_dfa *dfa)
+{
+ size_t i, state_count, trans_count;
+ int error = -EPROTO;
+
+ /* check that required tables exist */
+ if (!(dfa->tables[YYTD_ID_ACCEPT - 1] &&
+ dfa->tables[YYTD_ID_ACCEPT2 - 1] &&
+ dfa->tables[YYTD_ID_DEF - 1] &&
+ dfa->tables[YYTD_ID_BASE - 1] &&
+ dfa->tables[YYTD_ID_NXT - 1] && dfa->tables[YYTD_ID_CHK - 1]))
+ goto out;
+
+ /* accept.size == default.size == base.size */
+ state_count = dfa->tables[YYTD_ID_BASE - 1]->td_lolen;
+ if (!(state_count == dfa->tables[YYTD_ID_DEF - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT2 - 1]->td_lolen))
+ goto out;
+
+ /* next.size == chk.size */
+ trans_count = dfa->tables[YYTD_ID_NXT - 1]->td_lolen;
+ if (trans_count != dfa->tables[YYTD_ID_CHK - 1]->td_lolen)
+ goto out;
+
+ /* if equivalence classes then its table size must be 256 */
+ if (dfa->tables[YYTD_ID_EC - 1] &&
+ dfa->tables[YYTD_ID_EC - 1]->td_lolen != 256)
+ goto out;
+
+ for (i = 0; i < state_count; i++) {
+ if (DEFAULT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (BASE_TABLE(dfa)[i] >= trans_count + 256)


+ goto out;
+ }
+

+ for (i = 0; i < trans_count; i++) {
+ if (NEXT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (CHECK_TABLE(dfa)[i] >= state_count)


+ goto out;
+ }
+

+ /* verify accept permissions */
+ for (i = 0; i < state_count; i++) {
+ int mode = ACCEPT_TABLE(dfa)[i];
+
+ if (mode & ~DFA_VALID_PERM_MASK)
+ goto out;
+ if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)


+ goto out;
+
+ }

+
+ error = 0;


+out:
+ return error;
+}
+

+struct aa_dfa *aa_match_alloc(void)
+{
+ return kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
+}
+
+void aa_match_free(struct aa_dfa *dfa)
+{
+ if (dfa) {
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++)
+ free_table(dfa->tables[i]);
+ }
+ kfree(dfa);
+}
+
+/**
+ * aa_dfa_match_len - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
+ * @start: the state of the dfa to start matching in
+ * @str: the string of bytes to match against the dfa
+ * @len: length of the string of bytes to match
+ *
+ * aa_dfa_match_len will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ *
+ * This function will happily match again the 0 byte and only finishes
+ * when @len input is consumed.
+ */
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,


+ const char *str, int len)
+{

+ u16 *def = DEFAULT_TABLE(dfa);
+ u32 *base = BASE_TABLE(dfa);
+ u16 *next = NEXT_TABLE(dfa);
+ u16 *check = CHECK_TABLE(dfa);
+ unsigned int state = start, pos;
+
+ if (state == 0)
+ return 0;
+
+ /* current state is <state>, matching character *str */
+ if (dfa->tables[YYTD_ID_EC - 1]) {
+ u8 *equiv = EQUIV_TABLE(dfa);
+ for (; len; len--) {
+ pos = base[state] + equiv[(u8) *str++];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ } else {
+ for (; len; len--) {
+ pos = base[state] + (u8) *str++;
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ }
+ return state;
+}
+
+/**
+ * aa_dfa_next_state - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
+ * @start: the state of the dfa to start matching in
+ * @str: the null terminated string of bytes to match against the dfa
+ *
+ * aa_dfa_next_state will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ */
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str)
+{
+ return aa_dfa_match_len(dfa, start, str, strlen(str));
+}
+
+/**
+ * aa_dfa_null_transition - step to next state after null character
+ * @dfa: the dfa to match against
+ * @start: the state of the dfa to start matching in
+ *
+ * aa_dfa_null_transition transitions to the next state after a null
+ * character which is not used in standard matching and is only
+ * used to seperate pairs.
+ */
+unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start)
+{
+ return aa_dfa_match_len(dfa, start, "", 1);
+}
--
1.6.3.3

Message has been deleted
Message has been deleted
Message has been deleted

Tetsuo Handa

unread,
Nov 5, 2009, 12:49:54 AM11/5/09
to john.j...@canonical.com, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org
Hello.

I browsed using lxr.

> static int aa_audit_caps(struct aa_profile *profile, struct aa_audit_caps *sa)

..snipped...
> ent = &get_cpu_var(audit_cache);


> if (sa->base.task == ent->task && cap_raised(ent->caps, sa->cap)) {

put_cpu_var(audit_cache); ?

> if (PROFILE_COMPLAIN(profile))
> return 0;
> return sa->base.error;
> } else {


> ent->task = sa->base.task;

> cap_raise(ent->caps, sa->cap);
> }
> put_cpu_var(audit_cache);
..snipped...

Regarding unpack_*(), I'm not sure, but e seems to be no longer used after once
unpack_*() failed. If so, we can remove

> void *pos = e->pos;

and

> fail:
> e->pos = pos;

Also, please add comments regarding

memory allocated here is released by ...

refcount obtained here is released by ...

the caller of this function need to hold ... lock

as it is difficult for me to track memleak/refcounter/locking bugs.
For example, in function apparmor_dentry_open(), from

fcxt->profile = aa_get_profile(profile);

to something like

/* released by ... */
fcxt->profile = aa_get_profile(profile);

(Oh, is it correct to get refcount even if aa_path_perm() failed?)

Regards.

John Johansen

unread,
Nov 6, 2009, 6:50:34 PM11/6/09
to Tetsuo Handa, john.j...@canonical.com, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org
Tetsuo Handa wrote:
> Hello.
>
> I browsed using lxr.
>
>
>
>> static int aa_audit_caps(struct aa_profile *profile, struct aa_audit_caps *sa)
> ...snipped...

>> ent = &get_cpu_var(audit_cache);
>> if (sa->base.task == ent->task && cap_raised(ent->caps, sa->cap)) {
>
> put_cpu_var(audit_cache); ?
>
yep thanks for the catch

>> if (PROFILE_COMPLAIN(profile))
>> return 0;
>> return sa->base.error;
>> } else {
>> ent->task = sa->base.task;
>> cap_raise(ent->caps, sa->cap);
>> }
>> put_cpu_var(audit_cache);

> ...snipped...


>
>
>
> Regarding unpack_*(), I'm not sure, but e seems to be no longer used after once
> unpack_*() failed. If so, we can remove
>
>> void *pos = e->pos;
>
> and
>
>> fail:
>> e->pos = pos;
>

actually it is used sometimes for optional elements. However this could be
cleaned up some because optional elements should only ever fail on the
name or type tags, not the actual data it self.

It is also used in reporting failure position to user space but that only
gets the tag location, it might be better to return the true location of
failure, I'll have a look.

>
>
> Also, please add comments regarding
>
> memory allocated here is released by ...
>
> refcount obtained here is released by ...
>
> the caller of this function need to hold ... lock
>

will do

> as it is difficult for me to track memleak/refcounter/locking bugs.
> For example, in function apparmor_dentry_open(), from
>
> fcxt->profile = aa_get_profile(profile);
>
> to something like
>
> /* released by ... */
> fcxt->profile = aa_get_profile(profile);
>
> (Oh, is it correct to get refcount even if aa_path_perm() failed?)
>

yes as long as the refcount is put, there are several possible reasons for
grabbing a refcount, like passing the object to auditing, or just optimizing the success path.

Of course it could also just be a bug or code that could use some cleaning up
too.

Thanks again Tetsuo

john

Eric Paris

unread,
Nov 9, 2009, 10:20:57 AM11/9/09
to John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
On Tue, Nov 3, 2009 at 6:48 PM, John Johansen
<john.j...@canonical.com> wrote:
> AppArmor hooks to interface with the LSM, and module parameters and
> initialization.
>
> Signed-off-by: John Johansen <john.j...@canonical.com>
> ---

There is a reason we do the round_hint_to_min() stuff in the vm and we
recalculate that value every time dac_mmap_min_addr is change. It's
because mmap (NOT MAP_FIXED) with a hint < profile->mmap_min_addr is
going to end up getting denied here since the VM is going to assign it
the address it wanted instead of find a new address and you are going
to deny that task.

If profile() is a per task thing, I think you are in a failed
situation and can't solve the problem wtihout intrusive VFS hooks. If
profile is a global thing just update that global value. In either
case, this code is wrong....

-Eric

Eric Paris

unread,
Nov 9, 2009, 10:37:39 AM11/9/09
to John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
On Tue, Nov 3, 2009 at 6:48 PM, John Johansen
<john.j...@canonical.com> wrote:
> Update kenel audit range comments to show AppArmor's registered range of
> 1500-1599. �This range used to be reserved for LSPP but LSPP uses the
> SELinux range and the range was given to AppArmor.
> Patch is not in mainline -- pending AppArmor code submission to lkml
>
> Add the core routine for AppArmor auditing.
>
> Signed-off-by: John Johansen <john.j...@canonical.com>

As the audit maintainer I NAK. I NAK any patch that calls
audit_log_format() with %s. Use an audit_log_string() function unless
you can prove to me it meets all of the audit string handling rules
(and you know them). That part isn't too hard to fix but....

I'd like to register an objection to this patch as a whole. I know
it's a pain and its probably going to take a little reshaping of your
userspace tools that ran against your out of tree patches, but we get
a lot of work for free if you would make use of the lsm_audit.{c,h}
file instead of redoing everything. Extend it as you need to the same
way that SMACK and SELinux did. Personally I think it needs a generic
lsm=%s (SMACK does it in smack_log_callback, SELinux doesn't do it but
could/should)

I don't think we want to use more AUDIT messages for the same thing
even if someone in userspace said you could a long time ago.

LSM unification and code sharing is a good thing, even if the LSMs
can't agree on much else :)

-Eric

John Johansen

unread,
Nov 10, 2009, 11:13:22 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
This is the newest version of the AppArmor security module it has been
rewritten to use the security_path hooks instead of the previous vfs
approach. The current implementation is aimed at being as semantically
close to previous versions of AppArmor as possible while using the
existing LSM infrastructure.

The rewrite is functional and roughly equivalent to previous versions
of AppArmor based off of vfs patching. Development is on going and


improvements to file, capability, network, resource usage and ipc mediation
are planned.

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

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


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

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


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

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

Message has been deleted

John Johansen

unread,
Nov 10, 2009, 11:13:41 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
AppArmor contexts attach profiles and state to tasks, files, etc. when
a direct profile reference is not sufficient.

Signed-off-by: John Johansen <john.j...@canonical.com>
---
security/apparmor/context.c | 225 +++++++++++++++++++++++++++++++++++
security/apparmor/include/context.h | 145 ++++++++++++++++++++++


2 files changed, 370 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/context.c
create mode 100644 security/apparmor/include/context.h

diff --git a/security/apparmor/context.c b/security/apparmor/context.c
new file mode 100644
index 0000000..823207d
--- /dev/null
+++ b/security/apparmor/context.c
@@ -0,0 +1,225 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor functions used to manipulate object security
+ * contexts.

+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include "include/context.h"
+#include "include/policy.h"
+

+struct aa_task_context *aa_alloc_task_context(gfp_t flags)
+{
+ return kzalloc(sizeof(struct aa_task_context), flags);
+}
+
+void aa_free_task_context(struct aa_task_context *cxt)
+{
+ if (cxt) {
+ aa_put_profile(cxt->sys.profile);
+ aa_put_profile(cxt->sys.previous);
+ aa_put_profile(cxt->sys.onexec);
+
+ kzfree(cxt);
+ }

+}
+
+/**
+ * aa_dup_task_context - duplicate a task context, incrementing reference counts
+ * @new: a blank task context
+ * @old: the task context to copy
+ */
+void aa_dup_task_context(struct aa_task_context *new,
+ const struct aa_task_context *old)
+{
+ *new = *old;
+ aa_get_profile(new->sys.profile);
+ aa_get_profile(new->sys.previous);
+ aa_get_profile(new->sys.onexec);
+}
+
+/**


+ * aa_cred_policy - obtain cred's profiles
+ * @cred: cred to obtain profiles from

+ * @sys: return system profile
+ *


+ * does NOT increment reference count
+ */
+void aa_cred_policy(const struct cred *cred, struct aa_profile **sys)

+{
+ struct aa_task_context *cxt = cred->security;

+ BUG_ON(!cxt);
+ *sys = aa_confining_profile(cxt->sys.profile);
+}
+
+/**


+ * aa_get_task_policy - get the cred with the task policy, and current profiles
+ * @task: task to get policy of

+ * @sys: return - pointer to system profile
+ *
+ * Returns: a refcounted task cred


+ *
+ * Only gets the cred ref count which has ref counts on the profiles returned
+ */
+struct cred *aa_get_task_policy(const struct task_struct *task,
+ struct aa_profile **sys)

+{


+ struct cred *cred = get_task_cred(task);
+ aa_cred_policy(cred, sys);
+ return cred;

+}
+
+/**
+ * replace_group - replace a context group profile
+ * @cgrp: profile
+ * @profile: profile to replace cxt group
+ *
+ * Replace context grouping profile reference with @profile
+ */


+static void replace_group(struct aa_task_cxt_group *cgrp,

+ struct aa_profile *profile)
+{

+ if (cgrp->profile == profile)
+ return;
+
+ if (!profile || (profile->flags & PFLAG_UNCONFINED) ||
+ (cgrp->profile && cgrp->profile->ns != profile->ns)) {
+ aa_put_profile(cgrp->previous);
+ aa_put_profile(cgrp->onexec);
+ cgrp->previous = NULL;
+ cgrp->onexec = NULL;
+ cgrp->token = 0;
+ }
+ aa_put_profile(cgrp->profile);
+ cgrp->profile = aa_get_profile(profile);

+}
+
+/**


+ * aa_replace_current_profiles - replace the current tasks profiles

+ * @sys: new system profile
+ *


+ * Returns: error on failure
+ */
+int aa_replace_current_profiles(struct aa_profile *sys)

+{


+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)

+ return -ENOMEM;
+


+ cxt = new->security;
+ replace_group(&cxt->sys, sys);

+ /* todo add user group */
+
+ commit_creds(new);


+ return 0;
+}
+

+/**
+ * aa_set_current_onexec - set the tasks change_profile to happen onexec
+ * @sys: system profile to set at exec

+ *


+ * Returns: error on failure
+ */
+int aa_set_current_onexec(struct aa_profile *sys)

+{


+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)

+ return -ENOMEM;
+


+ cxt = new->security;
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = aa_get_profile(sys);
+
+ commit_creds(new);

+ return 0;
+}
+

+/**
+ * aa_set_current_hat - set the current tasks hat
+ * @profile: profile to set as the current hat

+ * @token: token value that must be specified to change from the hat
+ *


+ * Do switch of tasks hat. If the task is currently in a hat

+ * validate the token to match.
+ *


+ * Returns: error on failure
+ */
+int aa_set_current_hat(struct aa_profile *profile, u64 token)

+{


+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)

+ return -ENOMEM;
+


+ cxt = new->security;
+ if (!cxt->sys.previous) {
+ cxt->sys.previous = cxt->sys.profile;
+ cxt->sys.token = token;
+ } else if (cxt->sys.token == token) {
+ aa_put_profile(cxt->sys.profile);
+ } else {
+ /* previous_profile && cxt->token != token */
+ abort_creds(new);

+ return -EACCES;
+ }


+ cxt->sys.profile = aa_get_profile(profile);
+ /* clear exec on switching context */
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = NULL;
+
+ commit_creds(new);

+ return 0;
+}
+

+/**
+ * aa_restore_previous_profile - exit from hat context restoring the profile
+ * @token: the token that must be matched to exit hat context
+ *
+ * Attempt to return out of a hat to the previous profile. The token
+ * must match the stored token value.

+ *


+ * Returns: error of failure
+ */
+int aa_restore_previous_profile(u64 token)

+{


+ struct aa_task_context *cxt;
+ struct cred *new = prepare_creds();
+ if (!new)

+ return -ENOMEM;
+


+ cxt = new->security;
+ if (cxt->sys.token != token) {
+ abort_creds(new);

+ return -EACCES;
+ }


+ /* ignore restores when there is no saved profile */
+ if (!cxt->sys.previous) {
+ abort_creds(new);

+ return 0;
+ }
+

+ aa_put_profile(cxt->sys.profile);
+ cxt->sys.profile = aa_profile_newest(cxt->sys.previous);
+ if (unlikely(cxt->sys.profile != cxt->sys.previous)) {
+ aa_get_profile(cxt->sys.profile);
+ aa_put_profile(cxt->sys.previous);
+ }
+ /* clear exec && prev information when restoring to previous context */
+ cxt->sys.previous = NULL;
+ cxt->sys.token = 0;
+ aa_put_profile(cxt->sys.onexec);
+ cxt->sys.onexec = NULL;
+
+ commit_creds(new);
+ return 0;
+}
diff --git a/security/apparmor/include/context.h b/security/apparmor/include/context.h

new file mode 100644
index 0000000..ffc83c6
--- /dev/null
+++ b/security/apparmor/include/context.h
@@ -0,0 +1,145 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor contexts used to associate "labels" to objects.

+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_CONTEXT_H
+#define __AA_CONTEXT_H
+
+#include <linux/cred.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+
+#include "policy.h"
+
+/* struct aa_file_cxt - the AppArmor context the file was opened in
+ * @profile: the profile the file was opened under
+ * @perms: the permission the file was opened with
+ */
+struct aa_file_cxt {

+ struct aa_profile *profile;
+ u16 allowed;
+};
+


+static inline struct aa_file_cxt *aa_alloc_file_context(gfp_t gfp)
+{
+ return kzalloc(sizeof(struct aa_file_cxt), gfp);

+}
+


+static inline void aa_free_file_context(struct aa_file_cxt *cxt)
+{
+ aa_put_profile(cxt->profile);

+ kzfree(cxt);


+}
+
+/* struct aa_task_cxt_group - a grouping label data for confined tasks
+ * @profile: the current profile
+ * @exec: profile to transition to on next exec
+ * @previous: profile the task may return to
+ * @token: magic value the task must know for returning to @previous_profile
+ *
+ * Contains the task's current profile (which could change due to
+ * change_hat). Plus the hat_magic needed during change_hat.
+ */
+struct aa_task_cxt_group {

+ struct aa_profile *profile;


+ struct aa_profile *onexec;
+ struct aa_profile *previous;
+ u64 token;

+};
+
+/**


+ * struct aa_task_context - primary label for confined tasks
+ * @sys: the system labeling for the task

+ *


+ * A task is confined by the intersection of its system and user profiles
+ */
+struct aa_task_context {
+ struct aa_task_cxt_group sys;
+};
+
+struct aa_task_context *aa_alloc_task_context(gfp_t flags);
+void aa_free_task_context(struct aa_task_context *cxt);

+void aa_dup_task_context(struct aa_task_context *new,
+ const struct aa_task_context *old);


+void aa_cred_policy(const struct cred *cred, struct aa_profile **sys);
+struct cred *aa_get_task_policy(const struct task_struct *task,
+ struct aa_profile **sys);
+int aa_replace_current_profiles(struct aa_profile *sys);
+void aa_put_task_policy(struct cred *cred);
+int aa_set_current_onexec(struct aa_profile *sys);
+int aa_set_current_hat(struct aa_profile *profile, u64 token);
+int aa_restore_previous_profile(u64 cookie);
+
+static inline struct aa_task_context *__aa_task_cxt(struct task_struct *task)
+{
+ return __task_cred(task)->security;

+}
+
+/**


+ * __aa_task_is_confined - determine if @task has any confinement

+ * @task: task to check confinement of
+ *


+ * If @task != current needs to be in RCU safe critical section
+ */
+static inline int __aa_task_is_confined(struct task_struct *task)

+{


+ struct aa_task_context *cxt;
+ int rc = 1;
+
+ cxt = __aa_task_cxt(task);
+ if (!cxt || (cxt->sys.profile->flags & PFLAG_UNCONFINED))
+ rc = 0;
+

+ return rc;
+}
+

+static inline const struct cred *aa_current_policy(struct aa_profile **sys)
+{

+ const struct cred *cred = current_cred();


+ struct aa_task_context *cxt = cred->security;

+ BUG_ON(!cxt);
+ *sys = aa_confining_profile(cxt->sys.profile);


+
+ return cred;
+}
+
+static inline const struct cred *aa_current_policy_wupd(struct aa_profile **sys)
+{

+ const struct cred *cred = current_cred();


+ struct aa_task_context *cxt = cred->security;

+ BUG_ON(!cxt);
+
+ *sys = aa_profile_newest(cxt->sys.profile);
+ if (unlikely((cxt->sys.profile != *sys)))
+ aa_replace_current_profiles(*sys);

+ *sys = aa_filter_profile(*sys);
+
+ return cred;
+}
+
+static inline struct aa_profile *aa_current_profile(void)
+{
+ const struct cred *cred = current_cred();


+ struct aa_task_context *cxt = cred->security;

+ BUG_ON(!cxt);
+ return aa_confining_profile(cxt->sys.profile);
+}
+
+static inline struct aa_profile *aa_current_profile_wupd(void)
+{
+ struct aa_profile *p;
+ aa_current_policy_wupd(&p);


+ return p;
+}
+

+#endif /* __AA_CONTEXT_H */
--
1.6.3.3

--

Message has been deleted
Message has been deleted
Message has been deleted

John Johansen

unread,
Nov 10, 2009, 11:14:10 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen

set capability sys_admin,

Signed-off-by: John Johansen <john.j...@canonical.com>
---
security/apparmor/capability.c | 122 +++++++++++++++++++++++++++


security/apparmor/include/capability.h | 45 ++++++++++
security/apparmor/include/ipc.h | 28 ++++++
security/apparmor/include/net.h | 40 +++++++++
security/apparmor/include/resource.h | 45 ++++++++++

security/apparmor/ipc.c | 107 +++++++++++++++++++++++
security/apparmor/net.c | 145 ++++++++++++++++++++++++++++++++
security/apparmor/resource.c | 103 ++++++++++++++++++++++
8 files changed, 635 insertions(+), 0 deletions(-)


create mode 100644 security/apparmor/capability.c
create mode 100644 security/apparmor/include/capability.h
create mode 100644 security/apparmor/include/ipc.h
create mode 100644 security/apparmor/include/net.h
create mode 100644 security/apparmor/include/resource.h
create mode 100644 security/apparmor/ipc.c
create mode 100644 security/apparmor/net.c
create mode 100644 security/apparmor/resource.c

diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
new file mode 100644
index 0000000..bb218fd
--- /dev/null
+++ b/security/apparmor/capability.c
@@ -0,0 +1,122 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor capability mediation functions


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/capability.h>
+#include <linux/errno.h>
+#include <linux/gfp.h>
+
+#include "include/apparmor.h"

+#include "include/capability.h"
+#include "include/context.h"

+ if (sa->base.task == ent->task && cap_raised(ent->caps, sa->cap)) {
+ put_cpu_var(audit_cache);
+ if (PROFILE_COMPLAIN(profile))
+ return 0;


+ return sa->base.error;
+ } else {
+ ent->task = sa->base.task;
+ cap_raise(ent->caps, sa->cap);
+ }
+ put_cpu_var(audit_cache);
+
+ return aa_audit(type, profile, &sa->base, audit_cb);
+}
+
+int aa_profile_capable(struct aa_profile *profile, int cap)
+{
+ return cap_raised(profile->caps.allowed, cap) ? 0 : -EPERM;

+}
+
+/**


+ * aa_capable - test permission to use capability
+ * @task: task doing capability test against
+ * @profile: profile confining @task
+ * @cap: capability to be tested
+ * @audit: whether an audit record should be generated
+ *
+ * Look up capability in profile capability set.
+ * Returns 0 on success, or else an error code.
+ */
+int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap,
+ int audit)

+{


+ int error = aa_profile_capable(profile, cap);
+ struct aa_audit_caps sa = {
+ .base.operation = "capable",
+ .base.task = task,
+ .base.gfp_mask = GFP_ATOMIC,
+ .base.error = error,
+ .cap = cap,
+ };
+
+ if (!audit) {
+ if (PROFILE_COMPLAIN(profile))
+ return 0;

+ return error;
+ }
+

+ return aa_audit_caps(profile, &sa);
+}
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h

new file mode 100644


index 0000000..1343ee9
--- /dev/null
+++ b/security/apparmor/include/capability.h

@@ -0,0 +1,45 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor capability mediation definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

new file mode 100644


index 0000000..4fe96d3
--- /dev/null
+++ b/security/apparmor/include/ipc.h

@@ -0,0 +1,28 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor ipc mediation function definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_IPC_H
+#define __AA_IPC_H
+
+#include <linux/sched.h>
+
+struct aa_profile;
+
+int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,
+ struct aa_profile *tracee, unsigned int mode);
+
+int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,

+ unsigned int mode);
+

+#endif /* __AA_IPC_H */
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h

new file mode 100644


index 0000000..a3f6d05
--- /dev/null
+++ b/security/apparmor/include/net.h

@@ -0,0 +1,40 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor network mediation definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

new file mode 100644


index 0000000..4281041
--- /dev/null
+++ b/security/apparmor/include/resource.h

@@ -0,0 +1,45 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor resource limits function defintions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_RESOURCE_H
+#define __AA_RESOURCE_H
+
+#include <linux/resource.h>
+#include <linux/sched.h>
+
+struct aa_profile;
+
+/* struct aa_rlimit - rlimits settings for the profile
+ * @mask: which hard limits to set
+ * @limits: rlimit values that override task limits
+ *
+ * AppArmor rlimits are used to set confined task rlimits. Only the
+ * limits specified in @mask will be controlled by apparmor.
+ */
+struct aa_rlimit {
+ unsigned int mask;
+ struct rlimit limits[RLIM_NLIMITS];
+};
+

+int aa_task_setrlimit(struct aa_profile *profile, unsigned int resource,


+ struct rlimit *new_rlim);
+

+void __aa_transition_rlimits(struct aa_profile *old, struct aa_profile *new);
+
+static inline void aa_free_rlimit_rules(struct aa_rlimit *rlims)
+{
+ /* NOP */
+}
+
+#endif /* __AA_RESOURCE_H */
diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c

new file mode 100644
index 0000000..d3ae53a
--- /dev/null
+++ b/security/apparmor/ipc.c
@@ -0,0 +1,107 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor ipc mediation


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/gfp.h>
+#include <linux/ptrace.h>
+
+#include "include/audit.h"
+#include "include/capability.h"

+#include "include/context.h"
+#include "include/policy.h"
+

+struct aa_audit_ptrace {
+ struct aa_audit base;
+
+ pid_t tracer, tracee;
+};
+
+/* call back to audit ptrace fields */
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_ptrace *sa = va;
+ audit_log_format(ab, " tracer=%d tracee=%d", sa->tracer, sa->tracee);
+}
+
+static int aa_audit_ptrace(struct aa_profile *profile,
+ struct aa_audit_ptrace *sa)
+{
+ return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,
+ audit_cb);
+}
+
+int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer,

+ struct aa_profile *tracee, unsigned int mode)
+{


+ /* TODO: currently only based on capability, not extended ptrace
+ * rules,
+ * Test mode for PTRACE_MODE_READ || PTRACE_MODE_ATTACH
+ */
+
+ if (!tracer || tracer == tracee)
+ return 0;
+ /* log this capability request */
+ return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1);
+}
+
+int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee,

+ unsigned int mode)
+{

+ /*
+ * tracer can ptrace tracee when
+ * - tracer is unconfined ||
+ * - tracer & tracee are in the same namespace &&
+ * - tracer is in complain mode
+ * - tracer has rules allowing it to trace tracee currently this is:
+ * - confined by the same profile ||
+ * - tracer profile has CAP_SYS_PTRACE
+ */
+
+ struct aa_profile *tracer_p;

+ /* cred released below */
+ const struct cred *cred = aa_get_task_policy(tracer, &tracer_p);


+ int error = 0;
+

+ if (tracer_p) {
+ struct aa_audit_ptrace sa = {
+ .base.operation = "ptrace",
+ .base.gfp_mask = GFP_ATOMIC,
+ .tracer = tracer->pid,
+ .tracee = tracee->pid,
+ };
+ /* FIXME: different namespace restriction can be lifted
+ * if, namespace are matched to AppArmor namespaces
+ */
+ if (tracer->nsproxy != tracee->nsproxy) {
+ sa.base.info = "different namespaces";
+ sa.base.error = -EPERM;
+ aa_audit(AUDIT_APPARMOR_DENIED, tracer_p, &sa.base,
+ audit_cb);
+ } else {
+ struct aa_profile *tracee_p;

+ /* lcred released below */


+ struct cred *lcred = aa_get_task_policy(tracee,
+ &tracee_p);
+
+ sa.base.error = aa_may_ptrace(tracer, tracer_p,
+ tracee_p, mode);
+ sa.base.error = aa_audit_ptrace(tracer_p, &sa);
+
+ put_cred(lcred);
+ }
+ error = sa.base.error;
+ }
+ put_cred(cred);

+
+ return error;
+}

diff --git a/security/apparmor/net.c b/security/apparmor/net.c
new file mode 100644
index 0000000..254d52c
--- /dev/null
+++ b/security/apparmor/net.c


@@ -0,0 +1,145 @@
+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor network mediation


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+
+};
+

+ return -EINVAL;
+


+ if ((type < 0) || (type >= SOCK_MAX))

+ return -EINVAL;
+


+ /* unix domain and netlink sockets are handled by ipc */
+ if (family == AF_UNIX || family == AF_NETLINK)

+ return 0;
+


+ family_mask = profile->net.allowed[family];
+
+ sa.base.error = (family_mask & (1 << type)) ? 0 : -EACCES;
+
+ return aa_audit_net(profile, &sa);
+}
+
+int aa_revalidate_sk(struct sock *sk, char *operation)
+{

+ struct aa_profile *profile;
+ struct cred *cred;


+ int error = 0;
+

+ /* aa_revalidate_sk should not be called from interrupt context
+ * don't mediate these calls as they are not task related
+ */
+ if (in_interrupt())

+ return 0;
+
+ /* cred released below */
+ cred = aa_get_task_policy(current, &profile);
+ if (profile)


+ error = aa_net_perm(profile, operation,
+ sk->sk_family, sk->sk_type,
+ sk->sk_protocol);
+ put_cred(cred);

+
+ return error;
+}

diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c
new file mode 100644


index 0000000..3153784
--- /dev/null
+++ b/security/apparmor/resource.c

@@ -0,0 +1,103 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor resource mediation and attachment


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+}
+
+/**


+ * 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;
+

--

John Johansen

unread,
Nov 10, 2009, 11:14:15 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
AppArmor hooks to interface with the LSM, and module parameters and
initialization.

Signed-off-by: John Johansen <john.j...@canonical.com>
---
security/apparmor/lsm.c | 1063 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1063 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/lsm.c

diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c
new file mode 100644
index 0000000..6ee3480
--- /dev/null
+++ b/security/apparmor/lsm.c
@@ -0,0 +1,1063 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor LSM hooks.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/security.h>
+#include <linux/moduleparam.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/ptrace.h>
+#include <linux/ctype.h>
+#include <linux/sysctl.h>
+#include <linux/audit.h>
+#include <net/sock.h>
+
+#include "include/apparmor.h"
+#include "include/apparmorfs.h"

+#include "include/audit.h"
+#include "include/capability.h"
+#include "include/context.h"

+#include "include/file.h"
+#include "include/ipc.h"
+#include "include/net.h"
+#include "include/path.h"
+#include "include/policy.h"
+#include "include/procattr.h"
+
+/* Flag indicating whether initialization completed */
+int apparmor_initialized;
+
+/*
+ * LSM hook functions
+ */
+
+/*

+ * free the associated aa_task_context and put its profiles
+ */
+static void apparmor_cred_free(struct cred *cred)
+{

+ aa_free_task_context(cred->security);


+ cred->security = NULL;
+}

+
+/*
+ * allocate the apparmor part of blank credentials
+ */
+static int apparmor_cred_alloc_blank(struct cred *cred, gfp_t gfp)
+{
+ /* freed by apparmor_cred_free */
+ struct aa_task_context *cxt = aa_alloc_task_context(gfp);
+ if (cxt)
+ return -ENOMEM;
+
+ cred->security = cxt;


+ return 0;
+}
+

+/*
+ * prepare new aa_task_context for modification by prepare_cred block
+ */
+static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
+ gfp_t gfp)
+{

+ /* freed by apparmor_cred_free */
+ struct aa_task_context *cxt = aa_alloc_task_context(gfp);
+ if (!cxt)
+ return -ENOMEM;
+
+ aa_dup_task_context(cxt, old->security);


+ new->security = cxt;

+ return 0;
+}
+

+/*
+ * transfer the apparmor data to a blank set of creds
+ */
+static void apparmor_cred_transfer(struct cred *new, const struct cred *old)
+{
+ const struct aa_task_context *old_cxt = old->security;
+ struct aa_task_context *new_cxt = new->security;
+
+ aa_dup_task_context(new_cxt, old_cxt);


+}
+
+static int apparmor_ptrace_access_check(struct task_struct *child,

+ unsigned int mode)
+{

+ return aa_ptrace(current, child, mode);
+}
+
+static int apparmor_ptrace_traceme(struct task_struct *parent)
+{
+ return aa_ptrace(parent, current, PTRACE_MODE_ATTACH);
+}
+
+/* Derived from security/commoncap.c:cap_capget */
+static int apparmor_capget(struct task_struct *target, kernel_cap_t *effective,
+ kernel_cap_t *inheritable, kernel_cap_t *permitted)

+{
+ struct aa_profile *profile;

+ const struct cred *cred;
+
+ rcu_read_lock();
+ cred = __task_cred(target);
+ aa_cred_policy(cred, &profile);
+
+ *effective = cred->cap_effective;
+ *inheritable = cred->cap_inheritable;
+ *permitted = cred->cap_permitted;
+
+ if (profile) {
+ *effective = cap_combine(*effective, profile->caps.set);
+ *effective = cap_intersect(*effective, profile->caps.allowed);
+ }
+ rcu_read_unlock();
+

+ return 0;
+}
+

+static int apparmor_capable(struct task_struct *task, const struct cred *cred,
+ int cap, int audit)

+{
+ struct aa_profile *profile;

+ /* cap_capable returns 0 on success, else -EPERM */
+ int error = cap_capable(task, cred, cap, audit);
+
+ aa_cred_policy(cred, &profile);
+ if (profile && (!error || cap_raised(profile->caps.set, cap)))
+ error = aa_capable(task, profile, cap, audit);

+
+ return error;
+}
+

+static int apparmor_sysctl(struct ctl_table *table, int op)

+{
+ int error = 0;

+ struct aa_profile *profile = aa_current_profile_wupd();
+


+ if (profile) {
+ char *buffer, *name;
+ int mask;
+
+ mask = 0;
+ if (op & 4)
+ mask |= MAY_READ;
+ if (op & 2)
+ mask |= MAY_WRITE;
+
+ error = -ENOMEM;

+ /* freed below */


+ buffer = (char *)__get_free_page(GFP_KERNEL);
+ if (!buffer)
+ goto out;

+
+ /*


+ * TODO: convert this over to using a global or per
+ * namespace control instead of a hard coded /proc
+ */
+ name = sysctl_pathname(table, buffer, PAGE_SIZE);
+ if (name && name - buffer >= 5) {
+ struct path_cond cond = { 0, S_IFREG };
+ name -= 5;
+ memcpy(name, "/proc", 5);
+ error = aa_pathstr_perm(profile, "sysctl", name, mask,
+ &cond);
+ }
+ free_page((unsigned long)buffer);
+ }
+
+out:

+ return error;
+}
+

+static int common_perm(const char *op, struct path *path, u16 mask,
+ struct path_cond *cond)

+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ profile = aa_current_profile();
+ if (profile)


+ error = aa_path_perm(profile, op, path, mask, cond);

+
+ return error;
+}
+

+static int common_perm_dentry(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask,
+ struct path_cond *cond)
+{
+ struct path path = { dir->mnt, dentry };
+
+ return common_perm(op, &path, mask, cond);
+}
+
+static int common_perm_rm(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask)
+{
+ struct inode *inode = dentry->d_inode;
+ struct path_cond cond = { };
+
+ if (!dir->mnt || !inode || !mediated_filesystem(inode))

+ return 0;
+


+ cond.uid = inode->i_uid;
+ cond.mode = inode->i_mode;
+
+ return common_perm_dentry(op, dir, dentry, mask, &cond);
+}
+
+static int common_perm_create(const char *op, struct path *dir,
+ struct dentry *dentry, u16 mask, umode_t mode)
+{
+ struct path_cond cond = { current_fsuid(), mode };
+
+ if (!dir->mnt || !mediated_filesystem(dir->dentry->d_inode))

+ return 0;
+

+ return 0;


+ return common_perm("truncate", path, MAY_WRITE, &cond);
+}
+
+static int apparmor_path_symlink(struct path *dir, struct dentry *dentry,
+ const char *old_name)
+{
+ return common_perm_create("symlink_create", dir, dentry, AA_MAY_CREATE,
+ S_IFLNK);
+}
+
+static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
+ struct dentry *new_dentry)

+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+
+ profile = aa_current_profile_wupd();
+ if (profile)
+ error = aa_path_link(profile, old_dentry, new_dir, new_dentry);


+ return error;
+}
+

+static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
+ struct path *new_dir, struct dentry *new_dentry)

+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ if (!mediated_filesystem(old_dentry->d_inode))
+ return 0;
+


+ profile = aa_current_profile_wupd();
+ if (profile) {
+ struct path old_path = { old_dir->mnt, old_dentry };
+ struct path new_path = { new_dir->mnt, new_dentry };
+ struct path_cond cond = { old_dentry->d_inode->i_uid,
+ old_dentry->d_inode->i_mode

+ };
+


+ error = aa_path_perm(profile, "rename_src", &old_path,
+ MAY_READ | MAY_WRITE, &cond);
+ if (!error)
+ error = aa_path_perm(profile, "rename_dest", &new_path,
+ AA_MAY_CREATE | MAY_WRITE, &cond);

+
+ }
+ return error;
+}
+

+static int apparmor_dentry_open(struct file *file, const struct cred *cred)

+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ /* If in exec permission is handled by bprm hooks */
+ if (current->in_execve ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))

+ return 0;
+
+ aa_cred_policy(cred, &profile);
+ if (profile) {


+ struct aa_file_cxt *fcxt = file->f_security;
+ struct inode *inode = file->f_path.dentry->d_inode;
+ struct path_cond cond = { inode->i_uid, inode->i_mode };
+
+ error = aa_path_perm(profile, "open", &file->f_path,
+ aa_map_file_to_perms(file), &cond);

+ /* released by aa_free_file_context */


+ fcxt->profile = aa_get_profile(profile);
+ /* todo cache actual allowed permissions */
+ fcxt->allowed = 0;

+ }
+
+ return error;
+}
+

+static int apparmor_file_alloc_security(struct file *file)
+{

+ /* freed by apparmor_file_free_security */


+ file->f_security = aa_alloc_file_context(GFP_KERNEL);
+ if (!file->f_security)
+ return -ENOMEM;

+ return 0;
+
+}

+
+static void apparmor_file_free_security(struct file *file)
+{
+ struct aa_file_cxt *cxt = file->f_security;
+
+ aa_free_file_context(cxt);
+}
+
+static int apparmor_file_permission(struct file *file, int mask)

+{
+ /*


+ * TODO: cache profiles that have revalidated?
+ */
+ struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = fcxt->profile;

+ int error = 0;
+

+ if (!fprofile || !file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))

+ return 0;
+


+ profile = aa_current_profile();
+
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ /*
+ * AppArmor <= 2.4 revalidates files at access time instead
+ * of at exec.
+ */
+ if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
+ error = aa_file_perm(profile, "file_perm", file, mask);
+#endif

+
+ return error;
+}
+

+static int common_file_perm(const char *op, struct file *file, u16 mask)
+{
+ const struct aa_file_cxt *fcxt = file->f_security;
+ struct aa_profile *profile, *fprofile = fcxt->profile;

+ int error = 0;
+

+ if (!fprofile || !file->f_path.mnt ||
+ !mediated_filesystem(file->f_path.dentry->d_inode))

+ return 0;
+


+ profile = aa_current_profile_wupd();
+ if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))
+ error = aa_file_perm(profile, op, file, mask);

+
+ return error;
+}
+

+static int apparmor_file_lock(struct file *file, unsigned int cmd)
+{
+ u16 mask = AA_MAY_LOCK;
+
+ if (cmd == F_WRLCK)
+ mask |= MAY_WRITE;
+
+ return common_file_perm("file_lock", file, mask);
+}
+
+static int common_mmap(struct file *file, const char *operation,
+ unsigned long prot, unsigned long flags)
+{
+ struct dentry *dentry;

+ int mask = 0;
+


+ if (!file || !file->f_security)

+ return 0;
+


+ if (prot & PROT_READ)
+ mask |= MAY_READ;
+ /*
+ *Private mappings don't require write perms since they don't
+ * write back to the files
+ */
+ if ((prot & PROT_WRITE) && !(flags & MAP_PRIVATE))
+ mask |= MAY_WRITE;
+ if (prot & PROT_EXEC)
+ mask |= AA_EXEC_MMAP;
+
+ dentry = file->f_path.dentry;
+ return common_file_perm(operation, file, mask);
+}
+

+ return rc;
+
+ return common_mmap(file, "file_mmap", prot, flags);
+}


+
+static int apparmor_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+{
+ return common_mmap(vma->vm_file, "file_mprotect", prot,
+ !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
+}
+
+static int apparmor_getprocattr(struct task_struct *task, char *name,
+ char **value)

+{


+ int error = -ENOENT;
+ struct aa_namespace *ns;
+ struct aa_profile *profile, *onexec, *prev;

+ /* released below */
+ const struct cred *cred = aa_get_task_policy(task, &profile);


+ struct aa_task_context *cxt = cred->security;

+ ns = cxt->sys.profile->ns;
+ onexec = cxt->sys.onexec;
+ prev = cxt->sys.previous;
+
+ /* task must be either querying itself, unconfined or can ptrace */
+ if (current != task && profile && !capable(CAP_SYS_PTRACE)) {
+ error = -EPERM;
+ } else {
+ if (strcmp(name, "current") == 0) {
+ error = aa_getprocattr(ns, profile, value);
+ } else if (strcmp(name, "prev") == 0) {
+ if (prev)
+ error = aa_getprocattr(ns, prev, value);
+ } else if (strcmp(name, "exec") == 0) {
+ if (onexec)
+ error = aa_getprocattr(ns, onexec, value);
+ } else {
+ error = -EINVAL;
+ }
+ }

+
+ put_cred(cred);
+
+ return error;
+}

+
+static int apparmor_setprocattr(struct task_struct *task, char *name,
+ void *value, size_t size)
+{
+ char *command, *args;
+ int error;
+
+ if (size == 0 || size >= PAGE_SIZE)

+ return -EINVAL;
+


+ /* task can only write its own attributes */
+ if (current != task)

+ return -EACCES;
+


+ args = value;
+ args[size] = '\0';
+ args = strstrip(args);
+ command = strsep(&args, " ");
+ if (!args)
+ return -EINVAL;
+ while (isspace(*args))
+ args++;
+ if (!*args)

+ return -EINVAL;
+


+ if (strcmp(name, "current") == 0) {
+ if (strcmp(command, "changehat") == 0) {
+ error = aa_setprocattr_changehat(args, !AA_DO_TEST);
+ } else if (strcmp(command, "permhat") == 0) {
+ error = aa_setprocattr_changehat(args, AA_DO_TEST);
+ } else if (strcmp(command, "changeprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, 0,
+ !AA_DO_TEST);
+ } else if (strcmp(command, "permprofile") == 0) {
+ error = aa_setprocattr_changeprofile(args, 0,
+ AA_DO_TEST);
+ } else if (strcmp(command, "permipc") == 0) {
+ error = aa_setprocattr_permipc(args);
+ } else {

+ struct aa_audit sa = {

+ .operation = "setprocattr",
+ .gfp_mask = GFP_KERNEL,


+ .info = name,
+ .error = -EINVAL,
+ };
+ return aa_audit(AUDIT_APPARMOR_DENIED, NULL, &sa, NULL);
+ }
+ } else if (strcmp(name, "exec") == 0) {
+ error = aa_setprocattr_changeprofile(strstrip(args), 1,
+ !AA_DO_TEST);
+ } else {
+ /* only support the "current" and "exec" process attributes */

+ return -EINVAL;
+ }


+ if (!error)
+ error = size;

+ return error;
+}
+

+static int apparmor_task_setrlimit(unsigned int resource,


+ struct rlimit *new_rlim)
+{

+ struct aa_profile *profile = aa_current_profile_wupd();


+ int error = 0;
+

+ if (profile)
+ error = aa_task_setrlimit(profile, resource, new_rlim);

+
+ return error;
+}
+

+#ifdef CONFIG_SECURITY_APPARMOR_NETWORK
+static int apparmor_socket_create(int family, int type, int protocol, int kern)

+{
+ struct aa_profile *profile;

+ int error = 0;
+

+ if (kern)
+ return 0;
+
+ profile = aa_current_profile();
+ if (profile)


+ error = aa_net_perm(profile, "socket_create", family,
+ type, protocol);

+ return error;
+}
+

+static int apparmor_socket_post_create(struct socket *sock, int family,
+ int type, int protocol, int kern)
+{
+ struct sock *sk = sock->sk;
+
+ if (kern)

+ return 0;
+
+ return aa_revalidate_sk(sk, "socket_post_create");
+}
+


+static int apparmor_socket_bind(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_bind");
+}
+


+static int apparmor_socket_connect(struct socket *sock,
+ struct sockaddr *address, int addrlen)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_connect");
+}
+


+static int apparmor_socket_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_listen");
+}
+


+static int apparmor_socket_accept(struct socket *sock, struct socket *newsock)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_accept");
+}
+


+static int apparmor_socket_sendmsg(struct socket *sock,
+ struct msghdr *msg, int size)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_sendmsg");
+}
+


+static int apparmor_socket_recvmsg(struct socket *sock,
+ struct msghdr *msg, int size, int flags)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_recvmsg");
+}
+


+static int apparmor_socket_getsockname(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_getsockname");
+}
+


+static int apparmor_socket_getpeername(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_getpeername");
+}
+


+static int apparmor_socket_getsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_getsockopt");
+}
+


+static int apparmor_socket_setsockopt(struct socket *sock, int level,
+ int optname)
+{
+ struct sock *sk = sock->sk;
+

+ return aa_revalidate_sk(sk, "socket_setsockopt");
+}
+

+ .cred_alloc_blank = apparmor_cred_alloc_blank,


+ .cred_free = apparmor_cred_free,
+ .cred_prepare = apparmor_cred_prepare,

+ .cred_transfer = apparmor_cred_transfer,

+/* set global flag turning off the ability to load policy */

+ return 0;
+ }
+

+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)

+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+


+ if (strict_strtoul(val, 0, &l) || l != 0)

+ return -EINVAL;
+


+ apparmor_enabled = 0;
+ apparmor_disable();

+ return 0;
+}
+

+static int param_get_audit(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)

+ return -EINVAL;
+
+ return sprintf(buffer, "%s", audit_mode_names[aa_g_audit]);
+}
+


+static int param_set_audit(const char *val, struct kernel_param *kp)
+{
+ int i;
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)

+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+


+ for (i = 0; i < AUDIT_MAX_INDEX; i++) {
+ if (strcmp(val, audit_mode_names[i]) == 0) {
+ aa_g_audit = i;

+ return 0;
+ }
+ }

+


+ return -EINVAL;
+}
+

+static int param_get_mode(char *buffer, struct kernel_param *kp)
+{
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)

+ return -EINVAL;
+
+ return sprintf(buffer, "%s", profile_mode_names[aa_g_profile_mode]);
+}
+


+static int param_set_mode(const char *val, struct kernel_param *kp)
+{
+ int i;
+ if (__aa_task_is_confined(current))
+ return -EPERM;
+
+ if (!apparmor_enabled)

+ return -EINVAL;
+
+ if (!val)
+ return -EINVAL;
+


+ for (i = 0; i < APPARMOR_NAMES_MAX_INDEX; i++) {
+ if (strcmp(val, profile_mode_names[i]) == 0) {
+ aa_g_profile_mode = i;

+ return 0;
+ }
+ }

+


+ return -EINVAL;
+}
+

+/*
+ * AppArmor init functions
+ */

+static int __init set_init_cxt(void)
+{
+ struct cred *cred = (struct cred *)current->real_cred;


+ struct aa_task_context *cxt;
+

+ cxt = aa_alloc_task_context(GFP_KERNEL);
+ if (!cxt)

+ return -ENOMEM;
+


+ cxt->sys.profile = aa_get_profile(default_namespace->unconfined);
+ cred->security = cxt;
+

+ return 0;
+}
+

+static int __init apparmor_init(void)
+{
+ int error;
+
+ if (!apparmor_enabled || !security_module_enable(&apparmor_ops)) {
+ aa_info_message("AppArmor disabled by boot time parameter\n");
+ apparmor_enabled = 0;

+ return 0;
+ }
+

+ error = aa_alloc_default_namespace();
+ if (error) {
+ AA_ERROR("Unable to allocate default profile namespace\n");
+ goto alloc_out;

+ }
+


+ error = set_init_cxt();
+ if (error) {
+ AA_ERROR("Failed to set context on init task\n");
+ goto alloc_out;

+ }
+


+ error = register_security(&apparmor_ops);
+ if (error) {
+ AA_ERROR("Unable to register AppArmor\n");
+ goto register_security_out;
+ }
+
+ /* Report that AppArmor successfully initialized */
+ apparmor_initialized = 1;
+ if (aa_g_profile_mode == APPARMOR_COMPLAIN)
+ aa_info_message("AppArmor initialized: complain mode enabled");
+ else if (aa_g_profile_mode == APPARMOR_KILL)
+ aa_info_message("AppArmor initialized: kill mode enabled");
+ else
+ aa_info_message("AppArmor initialized");

+
+ return error;
+

+register_security_out:
+ aa_free_default_namespace();
+
+alloc_out:
+ aa_destroy_aafs();
+

+ apparmor_enabled = 0;


+ return error;
+
+}
+

+security_initcall(apparmor_init);
+
+void apparmor_disable(void)
+{
+ /* Remove and release all the profiles on the profile list. */
+ aa_profile_ns_list_release();
+
+ /* FIXME: cleanup profiles references on files */
+ aa_free_default_namespace();
+
+ aa_destroy_aafs();
+ apparmor_initialized = 0;
+
+ aa_info_message("AppArmor protection disabled");
+}
--
1.6.3.3

--

John Johansen

unread,
Nov 10, 2009, 11:14:23 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
Kconfig and Makefiles to enable configuration and building of AppArmor.

Signed-off-by: John Johansen <john.j...@canonical.com>
---

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

new file mode 100644

--

Message has been deleted
Message has been deleted

John Johansen

unread,
Nov 10, 2009, 11:15:48 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
A basic dfa matching engine based off the dfa engine in the Dragon
Book. It uses simple row compression with a check field.

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

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

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

Signed-off-by: John Johansen <john.j...@canonical.com>
---
security/apparmor/include/match.h | 104 +++++++++++++
security/apparmor/match.c | 301 +++++++++++++++++++++++++++++++++++++
2 files changed, 405 insertions(+), 0 deletions(-)


create mode 100644 security/apparmor/include/match.h
create mode 100644 security/apparmor/match.c

diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h
new file mode 100644


index 0000000..bd5015d
--- /dev/null
+++ b/security/apparmor/include/match.h

@@ -0,0 +1,104 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor policy dfa matching engine definitions.

+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

new file mode 100644
index 0000000..c76807b
--- /dev/null
+++ b/security/apparmor/match.c
@@ -0,0 +1,301 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor dfa based regular expression matching engine

+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+ goto out;
+


+ th.td_id = be16_to_cpu(*(u16 *) (blob));
+ th.td_flags = be16_to_cpu(*(u16 *) (blob + 2));
+ th.td_lolen = be32_to_cpu(*(u32 *) (blob + 8));
+ blob += sizeof(struct table_header);
+
+ if (!(th.td_flags == YYTD_DATA16 || th.td_flags == YYTD_DATA32 ||
+ th.td_flags == YYTD_DATA8))

+ goto out;
+


+ tsize = table_size(th.td_lolen, th.td_flags);
+ if (bsize < tsize)

+ goto out;
+
+ /* freed by free_table */


+ table = kmalloc(tsize, GFP_KERNEL);
+ if (!table)
+ table = vmalloc(tsize);
+ if (table) {
+ *table = th;
+ if (th.td_flags == YYTD_DATA8)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u8, byte_to_byte);
+ else if (th.td_flags == YYTD_DATA16)
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u16, be16_to_cpu);
+ else
+ UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
+ u32, be32_to_cpu);

+ }
+
+out:

+
+ return 0;
+

+fail:
+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
+ free_table(dfa->tables[i]);
+ dfa->tables[i] = NULL;

+ }
+ return error;
+}
+

+/**
+ * verify_dfa - verify that all the transitions and states in the dfa tables
+ * are in bounds.
+ * @dfa: dfa to test
+ *
+ * assumes dfa has gone through the verification done by unpacking
+ */
+int verify_dfa(struct aa_dfa *dfa)
+{
+ size_t i, state_count, trans_count;
+ int error = -EPROTO;
+
+ /* check that required tables exist */
+ if (!(dfa->tables[YYTD_ID_ACCEPT - 1] &&
+ dfa->tables[YYTD_ID_ACCEPT2 - 1] &&
+ dfa->tables[YYTD_ID_DEF - 1] &&
+ dfa->tables[YYTD_ID_BASE - 1] &&
+ dfa->tables[YYTD_ID_NXT - 1] && dfa->tables[YYTD_ID_CHK - 1]))

+ goto out;
+


+ /* accept.size == default.size == base.size */
+ state_count = dfa->tables[YYTD_ID_BASE - 1]->td_lolen;
+ if (!(state_count == dfa->tables[YYTD_ID_DEF - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT - 1]->td_lolen &&
+ state_count == dfa->tables[YYTD_ID_ACCEPT2 - 1]->td_lolen))

+ goto out;
+


+ /* next.size == chk.size */
+ trans_count = dfa->tables[YYTD_ID_NXT - 1]->td_lolen;
+ if (trans_count != dfa->tables[YYTD_ID_CHK - 1]->td_lolen)

+ goto out;
+


+ /* if equivalence classes then its table size must be 256 */
+ if (dfa->tables[YYTD_ID_EC - 1] &&
+ dfa->tables[YYTD_ID_EC - 1]->td_lolen != 256)

+ goto out;
+


+ for (i = 0; i < state_count; i++) {
+ if (DEFAULT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (BASE_TABLE(dfa)[i] >= trans_count + 256)

+ goto out;
+ }
+

+ for (i = 0; i < trans_count; i++) {
+ if (NEXT_TABLE(dfa)[i] >= state_count)
+ goto out;
+ if (CHECK_TABLE(dfa)[i] >= state_count)

+ goto out;
+ }
+

+ /* verify accept permissions */
+ for (i = 0; i < state_count; i++) {
+ int mode = ACCEPT_TABLE(dfa)[i];
+
+ if (mode & ~DFA_VALID_PERM_MASK)
+ goto out;
+ if (ACCEPT_TABLE2(dfa)[i] & ~DFA_VALID_PERM2_MASK)

+ goto out;
+
+ }

+
+ error = 0;

+out:
+ return error;
+}
+

+struct aa_dfa *aa_match_alloc(void)
+{
+ /* freed by aa_match_free, by caller */


+ return kzalloc(sizeof(struct aa_dfa), GFP_KERNEL);
+}
+
+void aa_match_free(struct aa_dfa *dfa)
+{
+ if (dfa) {
+ int i;

+


+ for (i = 0; i < ARRAY_SIZE(dfa->tables); i++)
+ free_table(dfa->tables[i]);
+ }
+ kfree(dfa);

+}
+
+/**


+ * aa_dfa_match_len - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
+ * @start: the state of the dfa to start matching in
+ * @str: the string of bytes to match against the dfa
+ * @len: length of the string of bytes to match
+ *
+ * aa_dfa_match_len will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.

+ *


+ * This function will happily match again the 0 byte and only finishes
+ * when @len input is consumed.
+ */
+unsigned int aa_dfa_match_len(struct aa_dfa *dfa, unsigned int start,
+ const char *str, int len)
+{
+ u16 *def = DEFAULT_TABLE(dfa);
+ u32 *base = BASE_TABLE(dfa);
+ u16 *next = NEXT_TABLE(dfa);
+ u16 *check = CHECK_TABLE(dfa);
+ unsigned int state = start, pos;
+
+ if (state == 0)

+ return 0;
+


+ /* current state is <state>, matching character *str */
+ if (dfa->tables[YYTD_ID_EC - 1]) {
+ u8 *equiv = EQUIV_TABLE(dfa);
+ for (; len; len--) {
+ pos = base[state] + equiv[(u8) *str++];
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ } else {
+ for (; len; len--) {
+ pos = base[state] + (u8) *str++;
+ if (check[pos] == state)
+ state = next[pos];
+ else
+ state = def[state];
+ }
+ }
+ return state;

+}
+
+/**


+ * aa_dfa_next_state - traverse @dfa to find state @str stops at
+ * @dfa: the dfa to match @str against
+ * @start: the state of the dfa to start matching in
+ * @str: the null terminated string of bytes to match against the dfa
+ *
+ * aa_dfa_next_state will match @str against the dfa and return the state it
+ * finished matching in. The final state can be used to look up the accepting
+ * label, or as the start state of a continuing match.
+ */
+unsigned int aa_dfa_match(struct aa_dfa *dfa, unsigned int start,
+ const char *str)
+{
+ return aa_dfa_match_len(dfa, start, str, strlen(str));

+}
+
+/**


+ * aa_dfa_null_transition - step to next state after null character
+ * @dfa: the dfa to match against
+ * @start: the state of the dfa to start matching in
+ *
+ * aa_dfa_null_transition transitions to the next state after a null
+ * character which is not used in standard matching and is only
+ * used to seperate pairs.
+ */
+unsigned int aa_dfa_null_transition(struct aa_dfa *dfa, unsigned int start)
+{
+ return aa_dfa_match_len(dfa, start, "", 1);
+}
--
1.6.3.3

--

John Johansen

unread,
Nov 10, 2009, 11:16:30 AM11/10/09
to linux-...@vger.kernel.org, linux-secu...@vger.kernel.org, John Johansen
Miscellaneous functions and defines needed by AppArmor, including
the base path resolution routines.

Signed-off-by: John Johansen <john.j...@canonical.com>
---

security/apparmor/include/apparmor.h | 61 +++++++++++
security/apparmor/include/path.h | 21 ++++
security/apparmor/lib.c | 76 +++++++++++++
security/apparmor/path.c | 196 ++++++++++++++++++++++++++++++++++
4 files changed, 354 insertions(+), 0 deletions(-)
create mode 100644 security/apparmor/include/apparmor.h
create mode 100644 security/apparmor/include/path.h
create mode 100644 security/apparmor/lib.c
create mode 100644 security/apparmor/path.c

diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h
new file mode 100644
index 0000000..1bf700f
--- /dev/null
+++ b/security/apparmor/include/apparmor.h
@@ -0,0 +1,61 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor basic global and lib definitions


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __APPARMOR_H
+#define __APPARMOR_H
+
+#include <linux/fs.h>
+
+/* Control parameters settable thru module/boot flags or
+ * via /sys/kernel/security/apparmor/control */
+extern enum audit_mode aa_g_audit;
+extern int aa_g_audit_header;
+extern int aa_g_debug;
+extern int aa_g_lock_policy;
+extern int aa_g_logsyscall;
+extern unsigned int aa_g_path_max;
+
+/*
+ * DEBUG remains global (no per profile flag) since it is mostly used in sysctl
+ * which is not related to profile accesses.
+ */
+
+#define AA_DEBUG(fmt, args...) \
+ do { \
+ if (aa_g_debug && printk_ratelimit()) \
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
+ } while (0)
+
+#define AA_ERROR(fmt, args...) \
+ do { \
+ if (printk_ratelimit()) \
+ printk(KERN_ERR "AppArmor: " fmt, ##args); \
+ } while (0)


+
+/* Flag indicating whether initialization completed */

+extern int apparmor_initialized;
+void apparmor_disable(void);
+
+/* fn's in lib */
+char *aa_split_name_from_ns(char *args, char **ns_name);
+int aa_strneq(const char *str, const char *sub, int len);
+char *aa_strchrnul(const char *s, int c);
+void aa_info_message(const char *str);
+
+static inline int mediated_filesystem(struct inode *inode)
+{
+ return !(inode->i_sb->s_flags & MS_NOUSER);
+}
+
+#endif /* __APPARMOR_H */
diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h
new file mode 100644
index 0000000..f146868
--- /dev/null
+++ b/security/apparmor/include/path.h
@@ -0,0 +1,21 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor basic path manipulation function definitions.


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#ifndef __AA_PATH_H
+#define __AA_PATH_H
+
+int aa_get_name(struct path *path, int is_dir, char **buffer, char **name);
+char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen);
+
+#endif /* __AA_PATH_H */
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
new file mode 100644
index 0000000..739705c
--- /dev/null
+++ b/security/apparmor/lib.c
@@ -0,0 +1,76 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains basic common functions used in AppArmor


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "include/audit.h"
+
+char *aa_strchrnul(const char *s, int c)
+{
+ for (; *s != (char)c && *s != '\0'; ++s)
+ ;
+ return (char *)s;
+}
+
+char *aa_split_name_from_ns(char *args, char **ns_name)
+{
+ char *name = strstrip(args);
+
+ *ns_name = NULL;
+ if (args[0] == ':') {
+ char *split = strstrip(strchr(&args[1], ':'));
+
+ if (!split)
+ return NULL;
+
+ *split = 0;
+ *ns_name = &args[1];
+ name = strstrip(split + 1);
+ }
+ if (*name == 0)
+ name = NULL;
+
+ return name;
+}
+
+/**
+ * aa_strneq - compare null terminated @str to a non null terminated substring
+ * @str: a null terminated string
+ * @sub: a substring, not necessarily null terminated
+ * @len: length of @sub to compare
+ *
+ * The @str string must be full consumed for this to be considered a match
+ */
+int aa_strneq(const char *str, const char *sub, int len)
+{
+ int res = strncmp(str, sub, len);
+ if (res)
+ return 0;
+ if (str[len] == 0)
+ return 1;


+ return 0;
+}
+

+void aa_info_message(const char *str)
+{


+ struct aa_audit sa = {

+ .gfp_mask = GFP_KERNEL,
+ .info = str,
+ };
+ printk(KERN_INFO "AppArmor: %s\n", str);
+ if (audit_enabled)
+ aa_audit(AUDIT_APPARMOR_STATUS, NULL, &sa, NULL);
+}
+
diff --git a/security/apparmor/path.c b/security/apparmor/path.c
new file mode 100644
index 0000000..c816c79
--- /dev/null
+++ b/security/apparmor/path.c
@@ -0,0 +1,196 @@


+/*
+ * AppArmor security module
+ *

+ * This file contains AppArmor function for pathnames


+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+

+#include <linux/mnt_namespace.h>
+#include <linux/mount.h>
+#include <linux/namei.h>
+#include <linux/path.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/fs_struct.h>
+
+#include "include/apparmor.h"
+#include "include/path.h"
+#include "include/policy.h"
+
+/**
+ * d_namespace_path - lookup a name associated with a given path
+ * @path: path to lookup
+ * @buf: buffer to store path to
+ * @buflen: length of @buf
+ * @name: returns pointer for start of path name with in @buf
+ * @flags: flags controling path lookup
+ *
+ */
+static int d_namespace_path(struct path *path, char *buf, int buflen,
+ char **name, int flags)
+{
+ struct path root, tmp, ns_root = { };
+ char *res;
+ int deleted;


+ int error = 0;
+

+ read_lock(&current->fs->lock);
+ root = current->fs->root;


+ /* released below */

+ path_get(&current->fs->root);
+ read_unlock(&current->fs->lock);
+ spin_lock(&vfsmount_lock);
+ if (root.mnt && root.mnt->mnt_ns)


+ /* released below */

+ ns_root.mnt = mntget(root.mnt->mnt_ns->root);
+ if (ns_root.mnt)


+ /* released below */

+ ns_root.dentry = dget(ns_root.mnt->mnt_root);
+ spin_unlock(&vfsmount_lock);
+ spin_lock(&dcache_lock);
+
+ /* There is a race window between path lookup here and the
+ * need to strip the " (deleted) string that __d_path applies
+ * Detect the race and relookup the path
+ */
+ do {
+ tmp = ns_root;
+ deleted = d_unlinked(path->dentry);
+ res = __d_path(path, &tmp, buf, buflen);
+
+ } while (deleted != d_unlinked(path->dentry));
+
+ *name = res;
+ /* handle error conditions - and still allow a partial path to
+ * be returned.
+ */
+ if (IS_ERR(res)) {
+ error = PTR_ERR(res);
+ *name = buf;
+ } else if (deleted) {
+ /* The stripping of (deleted) is a hack that could be removed
+ * with an updated __d_path
+ */
+
+ if (!path->dentry->d_inode || flags & PFLAG_DELETED_NAMES)
+ /* On some filesystems, newly allocated dentries appear
+ * to the security_path hooks as a deleted
+ * dentry except without an inode allocated.
+ *
+ * Remove the appended deleted text and return as a
+ * string for normal mediation. The (deleted) string
+ * is guarenteed to be added in this case, so just
+ * strip it.
+ */
+ buf[buflen - 11] = 0; /* - (len(" (deleted)") +\0) */
+ else
+ error = -ENOENT;
+ } else if (flags & ~PFLAG_CONNECT_PATH &&
+ tmp.dentry != ns_root.dentry && tmp.mnt != ns_root.mnt) {
+ /* disconnected path, don't return pathname starting with '/' */
+ error = -ESTALE;
+ if (*res == '/')
+ *name = res + 1;
+ }
+
+ spin_unlock(&dcache_lock);
+ path_put(&root);
+ path_put(&ns_root);


+
+ return error;
+}
+

+static int get_name_to_buffer(struct path *path, int is_dir, char *buffer,
+ int size, char **name, int flags)
+{
+ int error = d_namespace_path(path, buffer, size - is_dir, name, flags);
+
+ if (!error && is_dir && (*name)[1] != '\0')
+ /*
+ * Append "/" to the pathname. The root directory is a special
+ * case; it already ends in slash.
+ */
+ strcpy(&buffer[size - 2], "/");


+
+ return error;
+}
+
+/**

+ * aa_get_name - compute the pathname of a file
+ * @path: path the file
+ * @is_dir: set if the file is a directory
+ * @buffer: buffer that aa_get_name() allocated
+ * @name: the error code indicating whether aa_get_name failed
+ *
+ * Returns an error code if the there was a failure in obtaining the
+ * name.
+ *
+ * @name is apointer to the beginning of the pathname (which usually differs
+ * from the beginning of the buffer), or NULL. If there is an error @name
+ * may contain a partial or invalid name (in the case of a deleted file), that
+ * can be used for audit purposes, but it can not be used for mediation.
+ *
+ * We need @is_dir to indicate whether the file is a directory or not because
+ * the file may not yet exist, and so we cannot check the inode's file type.
+ */
+int aa_get_name(struct path *path, int is_dir, char **buffer, char **name)
+{
+ char *buf, *str = NULL;
+ int size = 256;
+ int error;
+
+ *name = NULL;
+ *buffer = NULL;
+ for (;;) {
+ /* freed by caller */
+ buf = kmalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ error = get_name_to_buffer(path, is_dir, buf, size, &str, 0);
+ if (!error || (error == -ENOENT) || (error == -ESTALE))
+ break;
+
+ kfree(buf);
+ size <<= 1;
+ if (size > aa_g_path_max)
+ return -ENAMETOOLONG;
+ }
+ *buffer = buf;
+ *name = str;


+
+ return error;
+}
+

+char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen)
+{
+ if (buflen < 1)
+ return NULL;
+ buffer += --buflen;
+ *buffer = '\0';
+
+ while (table) {
+ int namelen = strlen(table->procname);
+
+ if (buflen < namelen + 1)
+ return NULL;
+ buflen -= namelen + 1;
+ buffer -= namelen;
+ memcpy(buffer, table->procname, namelen);
+ *--buffer = '/';
+ table = table->parent;
+ }
+ if (buflen < 4)
+ return NULL;
+ buffer -= 4;
+ memcpy(buffer, "/sys", 4);
+
+ return buffer;
+}
--
1.6.3.3

Pekka Enberg

unread,
Nov 10, 2009, 11:30:04 AM11/10/09
to John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
Hi John,

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

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

Pekka

Andi Kleen

unread,
Nov 10, 2009, 11:44:41 AM11/10/09
to Pekka Enberg, John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
Pekka Enberg <pen...@cs.helsinki.fi> writes:

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

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

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

-Andi
--
a...@linux.intel.com -- Speaking for myself only.

Stephen Hemminger

unread,
Nov 10, 2009, 1:21:49 PM11/10/09
to Andi Kleen, Pekka Enberg, John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
On Tue, 10 Nov 2009 17:44:27 +0100
Andi Kleen <an...@firstfloor.org> wrote:

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

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

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

John Johansen

unread,
Nov 10, 2009, 1:38:42 PM11/10/09
to Eric Paris, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
yes that will be a pain but if that is what is needed then we will have
to live with it. However there is a caveat, that I need to look into yet,
all apparmor loggin will necessarily go through the audit subsystem.

We are planning our own dedicated netlink interface and dumping high volume
complain (learning) mode messages to it if an external application is
registered. I pretty sure we can make it work but I just haven't looked
at it enough yet.

thanks
john

John Johansen

unread,
Nov 10, 2009, 1:39:09 PM11/10/09
to Eric Paris, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
yep, thanks for pointing this out. I will look into it.

john

John Johansen

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

cheers
john

Stephen Hemminger

unread,
Nov 13, 2009, 12:45:00 PM11/13/09
to John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
On Tue, 10 Nov 2009 08:12:53 -0800
John Johansen <john.j...@canonical.com> wrote:

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

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

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

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

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

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

John Johansen

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

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

thanks
john

da...@lang.hm

unread,
Nov 15, 2009, 5:14:31 PM11/15/09
to Andi Kleen, Pekka Enberg, John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
On Tue, 10 Nov 2009, Andi Kleen wrote:

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

ubuntu has shipped with AppArmor for the last few releases.

David Lang

da...@lang.hm

unread,
Nov 15, 2009, 5:14:50 PM11/15/09
to Stephen Hemminger, Andi Kleen, Pekka Enberg, John Johansen, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
On Tue, 10 Nov 2009, Stephen Hemminger wrote:

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

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

David Lang

Message has been deleted
Message has been deleted

Tetsuo Handa

unread,
Nov 22, 2009, 6:50:13 AM11/22/09
to john.j...@canonical.com, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
And the rest of files...

Regarding match.c

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

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

.

> int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)

> {
(...snipped...)
> fail:


> for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {

> free_table(dfa->tables[i]);


> dfa->tables[i] = NULL;
> }

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

> return error;
> }

Regarding net.c

> static void audit_cb(struct audit_buffer *ab, void *va)

> {


> struct aa_audit_net *sa = va;

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

> static int aa_audit_net(struct aa_profile *profile, struct aa_audit_net *sa)

> {
(...snipped...)


> return aa_audit(type, profile, (struct aa_audit *)sa, audit_cb);

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

> }

Regarding policy.c

> struct aa_namespace *alloc_aa_namespace(const char *name)

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

> struct aa_namespace *aa_prepare_namespace(const char *name)

This function could be "static".

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

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

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

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

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

This function could be "static".

> void __aa_profile_list_release(struct list_head *head)

This function could be "static".

> void __aa_remove_namespace(struct aa_namespace *ns)

This function could be "static".

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

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

> }

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

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

Regarding policy_unpack.c

Please use bool for functions that return 0 or 1.

> static void audit_cb(struct audit_buffer *ab, void *va)

> {
> struct aa_audit_iface *sa = va;

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

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

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

> }

Regarding resource.c

> static void audit_cb(struct audit_buffer *ab, void *va)

> {


> struct aa_audit_resource *sa = va;

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

> static int aa_audit_resource(struct aa_profile *profile,
> struct aa_audit_resource *sa)
> {


> return aa_audit(AUDIT_APPARMOR_AUTO, profile, (struct aa_audit *)sa,

> audit_cb);

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

> }

Regarding sid.c

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

This function is not used.

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

This function is not used.

> struct aa_profile *aa_get_sid_profile(u32 sid)

This function is not used.

Message has been deleted

John Johansen

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

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

that is a possibility as long as it got a good comment to explain what
is going on.

>
> .
>
>
>
>> int unpack_dfa(struct aa_dfa *dfa, void *blob, size_t size)
>> {
> (...snipped...)
>> fail:
>> for (i = 0; i < ARRAY_SIZE(dfa->tables); i++) {
>> free_table(dfa->tables[i]);
>> dfa->tables[i] = NULL;
>> }
>
> This function is called by only aa_unpack_dfa(), and aa_unpack_dfa() calls
> aa_match_free(). Thus, you don't need to call free_table() here.
>

Hrmm, yeah it needs to be reworked, I don't particularly like returning a
partial struct to have it cleaned up later. I think it might be better
to rework aa_unpack_dfa, dropping the aa_match_free and moving the verify
call into the unpack routine, and the above for loop can be replaced
by aa_match_free


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

yep, thanks

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

will do

>
>
>> struct aa_namespace *aa_prepare_namespace(const char *name)
>
> This function could be "static".
>
>> {
>> struct aa_namespace *ns;
>>
>> write_lock(&ns_list_lock);
>> if (name)
>> /* released by caller */
>> ns = aa_get_namespace(__aa_find_namespace(&ns_list, name));
>> else
>> /* released by caller */
>> ns = aa_get_namespace(default_namespace);
>
> alloc_aa_namespace() returns NULL if name == NULL.
> If it is intended behavior, you may do like
>
> else {
> /* released by caller */
> ns = aa_get_namespace(default_namespace);
> write_unlock(&ns_list_lock);
> return ns;
> }
>

well at this point name != NULL because in that case ns == default_namespace,
but it should be documented that name can not be null here.

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

Yes, though I will give it another run through and reverify and add better
comments. The locking is such that the profile should be removed from
the list before free_aa_profile is called, and once in free_aa_profile
the lock is taken and released before any put_ is done.

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

yep

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

indeed, I should not have switched to kstrdup.

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

right will fix

thanks

John Johansen

unread,
Nov 23, 2009, 5:11:34 AM11/23/09
to Tetsuo Handa, linux-...@vger.kernel.org, linux-secu...@vger.kernel.org
Tetsuo Handa wrote:
> Regarding file.c ipc.c lib.c lsm.c
>
>
>
> You can use container_of() inside callback functions to avoid "void *".
>
yeah that is cleaner, will do

>> int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
>> void (*cb) (struct audit_buffer *, void *))
>
> int aa_audit(int type, struct aa_profile *profile, struct aa_audit *sa,
> void (*cb) (struct audit_buffer *, struct aa_audit *))
>
>> static int aa_audit_base(int type, struct aa_profile *profile,
>> struct aa_audit *sa, struct audit_context *audit_cxt,
>> void (*cb) (struct audit_buffer *, void *))
>
> static int aa_audit_base(int type, struct aa_profile *profile,
> struct aa_audit *sa, struct audit_context *audit_cxt,
> void (*cb) (struct audit_buffer *, struct aa_audit *))
>
>> void file_audit_cb(struct audit_buffer *ab, void *va)
>> {
>> struct aa_audit_file *sa = va;
>
> void file_audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {
> struct aa_audit_file *sa = container_of(va, struct aa_audit_file, base);
>
>> int aa_audit_file(struct aa_profile *profile, struct aa_audit_file *sa)
>> (...snipped...)
>> return aa_audit(type, profile, (struct aa_audit *)sa, file_audit_cb);
>
> return aa_audit(type, profile, &sa->base, file_audit_cb);


>
>> }
>
>> static void audit_cb(struct audit_buffer *ab, void *va)
>> {

>> struct aa_audit_ptrace *sa = va;


>
> static void audit_cb(struct audit_buffer *ab, struct aa_audit *va)
> {

> struct aa_audit_ptrace *sa = container_of(va, struct aa_audit_ptrace,
> base);
>
>> static int aa_audit_ptrace(struct aa_profile *profile,
>> struct aa_audit_ptrace *sa)


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

>> int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
>> struct path *new_dir, struct dentry *new_dentry)
> (.,..snipped...)
>> unsigned int state;
> (.,..snipped...)
>> sa.perms = aa_str_perms(profile->file.dfa, DFA_START, sa.name, &cond,
>> &state);
>
> "state" remains uninitialized if profile->file.dfa == NULL.
> Are you sure profile->file.dfa != NULL ?
>
No this needs to be fixed.

>
>
>> char *aa_strchrnul(const char *s, int c)

>> {


>> for (; *s != (char)c && *s != '\0'; ++s)

>> ;
>> return (char *)s;
>> }
>
> Only fqname_subname() calls aa_strchrnul() and
> fqname_subname() returns NULL if aa_strchrnul() returns '\0'.
> You can use strchr() instead.
>
hrmm right thanks

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

>> name = strstrip(split + 1);
>> }

>> for (split = strstr(name, "//"); split; split = strstr(name, "//"))
>> name = split + 2;
>>
>> return name;
>> }
>
>
>
>> char *aa_split_name_from_ns(char *args, char **ns_name)
>> {
>> char *name = strstrip(args);
>>
>> *ns_name = NULL;


>> if (args[0] == ':') {

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

>> if (!split)
>> return NULL;
>
>
> strchr() returns NULL if not found, and strstrip(NULL) will do strlen(NULL).
> strstrip() never returns NULL. Did you mean
>
> char *split = strchr(&args[1], ':');
>
> if (!split)
> return NULL;
> split = strstrip(split);
>
> ?
yes, thanks

>
>> *split = 0;
>> *ns_name = &args[1];


>> name = strstrip(split + 1);
>> }

>> if (*name == 0)
>> name = NULL;
>>
>> return name;


>> }
>
>
>
>> static int apparmor_sysctl(struct ctl_table *table, int op)

> This hook will be removed.


>
>> char *sysctl_pathname(struct ctl_table *table, char *buffer, int buflen)

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

>
>
>> static int apparmor_file_permission(struct file *file, int mask)

>> {
>> /*


>> * TODO: cache profiles that have revalidated?

>> */


>> struct aa_file_cxt *fcxt = file->f_security;

>> struct aa_profile *profile, *fprofile = fcxt->profile;

>> int error = 0;
>>
>> if (!fprofile || !file->f_path.mnt ||
>> !mediated_filesystem(file->f_path.dentry->d_inode))
>> return 0;
>>
>> profile = aa_current_profile();
>>
>> #ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
>> /*


>> * AppArmor <= 2.4 revalidates files at access time instead

>> * of at exec.
>> */


>> if (profile && ((fprofile != profile) || (mask & ~fcxt->allowed)))

>> error = aa_file_perm(profile, "file_perm", file, mask);

>> #endif
>>
>> return error;
>> }
>
> Why need to call this function if CONFIG_SECURITY_APPARMOR_COMPAT_24=n ?
> I think we can do
>
>> static struct security_operations apparmor_ops = {
> (...snipped...)
>
> #ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
>
>> .file_permission = apparmor_file_permission,
>
> #endif
>
yes we can currently. Though this will change in the future, but for now
we should got with the cleaner switch.

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

>
>
>> static int __init apparmor_init(void)
> (...snipped...)
>> error = set_init_cxt();
>> if (error) {


>> AA_ERROR("Failed to set context on init task\n");

>> goto alloc_out;
>
> This should be
>
> goto register_security_out;
>
> in order to call aa_free_default_namespace().
>
>> }

indeed

thanks again for taking the time to review
john

0 new messages