2009-11-03 23:48:36

by John Johansen

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

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

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


2009-11-03 23:49:43

by John Johansen

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

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

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/apparmor.h | 61 ++++++++++++
security/apparmor/include/path.h | 21 ++++
security/apparmor/lib.c | 76 +++++++++++++++
security/apparmor/path.c | 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

2009-11-03 23:52:44

by John Johansen

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

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

Add the core routine for AppArmor auditing.

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

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

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

2009-11-03 23:49:51

by John Johansen

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

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

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/context.c | 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

2009-11-03 23:49:53

by John Johansen

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

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

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

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/policy.h | 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

2009-11-03 23:52:18

by John Johansen

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

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

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

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

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

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/include/match.h | 104 +++++++++++++
security/apparmor/match.c | 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

2009-11-03 23:50:01

by John Johansen

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

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

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

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

diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h
new file mode 100644
index 0000000..d5613b2
--- /dev/null
+++ b/security/apparmor/include/policy_unpack.h
@@ -0,0 +1,22 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor policy loading interface function definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __POLICY_INTERFACE_H
+#define __POLICY_INTERFACE_H
+
+ssize_t aa_interface_add_profiles(void *data, size_t size);
+ssize_t aa_interface_replace_profiles(void *udata, size_t size);
+ssize_t aa_interface_remove_profiles(char *name, size_t size);
+
+#endif /* __POLICY_INTERFACE_H */
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
new file mode 100644
index 0000000..e1ee015
--- /dev/null
+++ b/security/apparmor/policy_unpack.c
@@ -0,0 +1,854 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor functions for unpacking policy loaded from
+ * userspace.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor uses a serialized binary format for loading policy.
+ * The policy format is documented in Documentation/???
+ * All policy is validated all before it is used.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/errno.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/match.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+#include "include/sid.h"
+
+/*
+ * The AppArmor interface treats data as a type byte followed by the
+ * actual data. The interface has the notion of a a named entry
+ * which has a name (AA_NAME typecode followed by name string) followed by
+ * the entries typecode and data. Named types allow for optional
+ * elements and extensions to be added and tested for without breaking
+ * backwards compatability.
+ */
+
+enum aa_code {
+ AA_U8,
+ AA_U16,
+ AA_U32,
+ AA_U64,
+ AA_NAME, /* same as string except it is items name */
+ AA_STRING,
+ AA_BLOB,
+ AA_STRUCT,
+ AA_STRUCTEND,
+ AA_LIST,
+ AA_LISTEND,
+ AA_ARRAY,
+ AA_ARRAYEND,
+};
+
+/*
+ * aa_ext is the read of the buffer containing the serialized profile. The
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
+ * the unpack routines.
+ */
+struct aa_ext {
+ void *start;
+ void *end;
+ void *pos; /* pointer to current position in the buffer */
+ u32 version;
+ char *ns_name;
+};
+
+struct aa_audit_iface {
+ struct aa_audit base;
+
+ const char *name;
+ const char *name2;
+ const struct aa_ext *e;
+};
+
+static void aa_audit_init(struct aa_audit_iface *sa, const char *operation,
+ struct aa_ext *e)
+{
+ memset(sa, 0, sizeof(*sa));
+ sa->base.operation = operation;
+ sa->base.gfp_mask = GFP_KERNEL;
+ sa->e = e;
+}
+
+static void audit_cb(struct audit_buffer *ab, void *va)
+{
+ struct aa_audit_iface *sa = va;
+
+ if (sa->name)
+ audit_log_format(ab, " name=%s", sa->name);
+ if (sa->name2)
+ audit_log_format(ab, " namespace=%s", sa->name2);
+ if (sa->base.error && sa->e) {
+ long len = sa->e->pos - sa->e->start;
+ audit_log_format(ab, " offset=%ld", len);
+ }
+}
+
+static int aa_audit_iface(struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile;
+ struct cred *cred = aa_get_task_policy(current, &profile);
+ int error = aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa->base,
+ audit_cb);
+ put_cred(cred);
+ return error;
+}
+
+static int aa_inbounds(struct aa_ext *e, size_t size)
+{
+ return (size <= e->end - e->pos);
+}
+
+/**
+ * aa_u16_chunck - test and do bounds checking for a u16 size based chunk
+ * @e: serialized data read head
+ * @chunk: start address for chunk of data
+ *
+ * return the size of chunk found with the read head at the end of
+ * the chunk.
+ */
+static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
+{
+ void *pos = e->pos;
+ size_t size = 0;
+
+ if (!aa_inbounds(e, sizeof(u16)))
+ goto fail;
+ size = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ if (!aa_inbounds(e, size))
+ goto fail;
+ *chunk = e->pos;
+ e->pos += size;
+ return size;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_X(struct aa_ext *e, enum aa_code code)
+{
+ if (!aa_inbounds(e, 1))
+ return 0;
+ if (*(u8 *) e->pos != code)
+ return 0;
+ e->pos++;
+ return 1;
+}
+
+/**
+ * unpack_nameX - check is the next element is of type X with a name of @name
+ * @e: serialized data extent information
+ * @code: type code
+ * @name: name to match to the serialized element.
+ *
+ * check that the next serialized data element is of type X and has a tag
+ * name @name. If @name is specified then there must be a matching
+ * name element in the stream. If @name is NULL any name element will be
+ * skipped and only the typecode will be tested.
+ * returns 1 on success (both type code and name tests match) and the read
+ * head is advanced past the headers
+ * returns %0 if either match failes, the read head does not move
+ */
+static int unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
+{
+ void *pos = e->pos;
+ /*
+ * Check for presence of a tagname, and if present name size
+ * AA_NAME tag value is a u16.
+ */
+ if (unpack_X(e, AA_NAME)) {
+ char *tag = NULL;
+ size_t size = unpack_u16_chunk(e, &tag);
+ /* if a name is specified it must match. otherwise skip tag */
+ if (name && (!size || strcmp(name, tag)))
+ goto fail;
+ } else if (name) {
+ /* if a name is specified and there is no name tag fail */
+ goto fail;
+ }
+
+ /* now check if type code matches */
+ if (unpack_X(e, code))
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_u16(struct aa_ext *e, u16 *data, const char *name)
+{
+ void *pos = e->pos;
+ if (unpack_nameX(e, AA_U16, name)) {
+ if (!aa_inbounds(e, sizeof(u16)))
+ goto fail;
+ if (data)
+ *data = le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return 1;
+ }
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_u32(struct aa_ext *e, u32 *data, const char *name)
+{
+ void *pos = e->pos;
+ if (unpack_nameX(e, AA_U32, name)) {
+ if (!aa_inbounds(e, sizeof(u32)))
+ goto fail;
+ if (data)
+ *data = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ return 1;
+ }
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_u64(struct aa_ext *e, u64 *data, const char *name)
+{
+ void *pos = e->pos;
+ if (unpack_nameX(e, AA_U64, name)) {
+ if (!aa_inbounds(e, sizeof(u64)))
+ goto fail;
+ if (data)
+ *data = le64_to_cpu(get_unaligned((u64 *) e->pos));
+ e->pos += sizeof(u64);
+ return 1;
+ }
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static size_t unpack_array(struct aa_ext *e, const char *name)
+{
+ void *pos = e->pos;
+ if (unpack_nameX(e, AA_ARRAY, name)) {
+ int size;
+ if (!aa_inbounds(e, sizeof(u16)))
+ goto fail;
+ size = (int)le16_to_cpu(get_unaligned((u16 *) e->pos));
+ e->pos += sizeof(u16);
+ return size;
+ }
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
+{
+ void *pos = e->pos;
+ if (unpack_nameX(e, AA_BLOB, name)) {
+ u32 size;
+ if (!aa_inbounds(e, sizeof(u32)))
+ goto fail;
+ size = le32_to_cpu(get_unaligned((u32 *) e->pos));
+ e->pos += sizeof(u32);
+ if (aa_inbounds(e, (size_t) size)) {
+ *blob = e->pos;
+ e->pos += size;
+ return size;
+ }
+ }
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_string(struct aa_ext *e, char **string, const char *name)
+{
+ char *src_str;
+ size_t size = 0;
+ void *pos = e->pos;
+ *string = NULL;
+ if (unpack_nameX(e, AA_STRING, name)) {
+ size = unpack_u16_chunk(e, &src_str);
+ if (size) {
+ /* strings are null terminated, length is size - 1 */
+ if (src_str[size - 1] != 0)
+ goto fail;
+ *string = src_str;
+ }
+ }
+
+ return size;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+static int unpack_dynstring(struct aa_ext *e, char **string, const char *name)
+{
+ char *tmp;
+ void *pos = e->pos;
+ int res = unpack_string(e, &tmp, name);
+ *string = NULL;
+
+ if (!res)
+ return res;
+
+ *string = kstrdup(tmp, GFP_KERNEL);
+ if (!*string) {
+ e->pos = pos;
+ return 0;
+ }
+
+ return res;
+}
+
+/**
+ * aa_unpack_dfa - unpack a file rule dfa
+ * @e: serialized data extent information
+ *
+ * returns dfa or ERR_PTR
+ */
+static struct aa_dfa *aa_unpack_dfa(struct aa_ext *e)
+{
+ char *blob = NULL;
+ size_t size, error = 0;
+ struct aa_dfa *dfa = NULL;
+
+ size = unpack_blob(e, &blob, "aadfa");
+ if (size) {
+ dfa = aa_match_alloc();
+ if (dfa) {
+ /*
+ * The dfa is aligned with in the blob to 8 bytes
+ * from the beginning of the stream.
+ */
+ size_t sz = blob - (char *)e->start;
+ size_t pad = ALIGN(sz, 8) - sz;
+ error = unpack_dfa(dfa, blob + pad, size - pad);
+ if (!error)
+ error = verify_dfa(dfa);
+ } else {
+ error = -ENOMEM;
+ }
+
+ if (error) {
+ aa_match_free(dfa);
+ dfa = ERR_PTR(error);
+ }
+ }
+
+ return dfa;
+}
+
+static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* exec table is optional */
+ if (unpack_nameX(e, AA_STRUCT, "xtable")) {
+ int i, size;
+
+ size = unpack_array(e, NULL);
+ /* currently 4 exec bits and entries 0-3 are reserved iupcx */
+ if (size > 16 - 4)
+ goto fail;
+ profile->file.trans.table = kzalloc(sizeof(char *) * size,
+ GFP_KERNEL);
+ if (!profile->file.trans.table)
+ goto fail;
+
+ for (i = 0; i < size; i++) {
+ char *tmp;
+ if (!unpack_dynstring(e, &tmp, NULL))
+ goto fail;
+ /*
+ * note: strings beginning with a : have an embedded
+ * \0 seperating the profile ns name from the profile
+ * name
+ */
+ profile->file.trans.table[i] = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ profile->file.trans.size = size;
+ }
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+int aa_unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* rlimits are optional */
+ if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
+ int i, size;
+ u32 tmp = 0;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.mask = tmp;
+
+ size = unpack_array(e, NULL);
+ if (size > RLIM_NLIMITS)
+ goto fail;
+ for (i = 0; i < size; i++) {
+ u64 tmp = 0;
+ if (!unpack_u64(e, &tmp, NULL))
+ goto fail;
+ profile->rlimits.limits[i].rlim_max = tmp;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/**
+ * aa_unpack_profile - unpack a serialized profile
+ * @e: serialized data extent information
+ * @sa: audit struct for the operation
+ */
+static struct aa_profile *aa_unpack_profile(struct aa_ext *e,
+ struct aa_audit_iface *sa)
+{
+ struct aa_profile *profile = NULL;
+ char *name;
+ size_t size = 0;
+ int i, error = -EPROTO;
+ u32 tmp;
+ u64 tmp64;
+
+ /* check that we have the right struct being passed */
+ if (!unpack_nameX(e, AA_STRUCT, "profile"))
+ goto fail;
+ if (!unpack_string(e, &name, NULL))
+ goto fail;
+
+ profile = alloc_aa_profile(name);
+ if (!profile)
+ return ERR_PTR(-ENOMEM);
+
+ /* xmatch is optional and may be NULL */
+ profile->xmatch = aa_unpack_dfa(e);
+ if (IS_ERR(profile->xmatch)) {
+ error = PTR_ERR(profile->xmatch);
+ profile->xmatch = NULL;
+ goto fail;
+ }
+ /* xmatch_len is not optional is xmatch is set */
+ if (profile->xmatch && !unpack_u32(e, &tmp, NULL))
+ goto fail;
+ profile->xmatch_len = tmp;
+
+ /* per profile debug flags (complain, audit) */
+ if (!unpack_nameX(e, AA_STRUCT, "flags"))
+ goto fail;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->flags |= PFLAG_HAT;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->mode = APPARMOR_COMPLAIN;
+ if (!unpack_u32(e, &tmp, NULL))
+ goto fail;
+ if (tmp)
+ profile->audit = AUDIT_ALL;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ /* mmap_min_addr is optional */
+ if (unpack_u64(e, &tmp64, "mmap_min_addr")) {
+ profile->mmap_min_addr = (unsigned long)tmp64;
+ if (((u64) profile->mmap_min_addr) == tmp64) {
+ profile->flags |= PFLAG_MMAP_MIN_ADDR;
+ } else {
+ sa->base.info = "invalid set mmap_min_addr";
+ goto fail;
+ }
+ }
+
+ if (!unpack_u32(e, &(profile->caps.allowed.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.set.cap[0]), NULL))
+ goto fail;
+
+ if (unpack_nameX(e, AA_STRUCT, "caps64")) {
+ /* optional upper half of 64 bit caps */
+ if (!unpack_u32(e, &(profile->caps.allowed.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
+ goto fail;
+ if (!unpack_u32(e, &(profile->caps.set.cap[1]), NULL))
+ goto fail;
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+ }
+
+ if (!aa_unpack_rlimits(e, profile))
+ goto fail;
+
+ size = unpack_array(e, "net_allowed_af");
+ if (size) {
+ if (size > AF_MAX)
+ goto fail;
+
+ for (i = 0; i < size; i++) {
+ if (!unpack_u16(e, &profile->net.allowed[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.audit[i], NULL))
+ goto fail;
+ if (!unpack_u16(e, &profile->net.quiet[i], NULL))
+ goto fail;
+ }
+ if (!unpack_nameX(e, AA_ARRAYEND, NULL))
+ goto fail;
+ /*
+ * allow unix domain and netlink sockets they are handled
+ * by IPC
+ */
+ }
+ profile->net.allowed[AF_UNIX] = 0xffff;
+ profile->net.allowed[AF_NETLINK] = 0xffff;
+
+ /* get file rules */
+ profile->file.dfa = aa_unpack_dfa(e);
+ if (IS_ERR(profile->file.dfa)) {
+ error = PTR_ERR(profile->file.dfa);
+ profile->file.dfa = NULL;
+ goto fail;
+ }
+
+ if (!aa_unpack_trans_table(e, profile))
+ goto fail;
+
+ if (!unpack_nameX(e, AA_STRUCTEND, NULL))
+ goto fail;
+
+ return profile;
+
+fail:
+ sa->name = profile && profile->base.name ? profile->base.name :
+ "unknown";
+ if (!sa->base.info)
+ sa->base.info = "failed to unpack profile";
+ aa_audit_iface(sa);
+
+ free_aa_profile(profile);
+
+ return ERR_PTR(error);
+}
+
+/**
+ * aa_verify_head - unpack serialized stream header
+ * @e: serialized data read head
+ * @operation: operation header is being verified for
+ *
+ * returns error or 0 if header is good
+ */
+static int aa_verify_header(struct aa_ext *e, struct aa_audit_iface *sa)
+{
+ /* get the interface version */
+ if (!unpack_u32(e, &e->version, "version")) {
+ sa->base.info = "invalid profile format";
+ aa_audit_iface(sa);
+ return -EPROTONOSUPPORT;
+ }
+
+ /* check that the interface version is currently supported */
+ if (e->version != 5) {
+ sa->base.info = "unsupported interface version";
+ aa_audit_iface(sa);
+ return -EPROTONOSUPPORT;
+ }
+
+ /* read the namespace if present */
+ if (!unpack_string(e, &e->ns_name, "namespace"))
+ e->ns_name = NULL;
+
+ return 0;
+}
+
+/**
+ * aa_interface_add_profiles - Unpack and add new profile(s) to the profile list
+ * @data: serialized data stream
+ * @size: size of the serialized data stream
+ */
+ssize_t aa_interface_add_profiles(void *data, size_t size)
+{
+ struct aa_profile *profile;
+ struct aa_namespace *ns = NULL;
+ struct aa_policy_common *common;
+ struct aa_ext e = {
+ .start = data,
+ .end = data + size,
+ .pos = data,
+ .ns_name = NULL
+ };
+ ssize_t error;
+ struct aa_audit_iface sa;
+ aa_audit_init(&sa, "profile_load", &e);
+
+ error = aa_verify_header(&e, &sa);
+ if (error)
+ return error;
+
+ profile = aa_unpack_profile(&e, &sa);
+ if (IS_ERR(profile))
+ return PTR_ERR(profile);
+
+ sa.name2 = e.ns_name;
+ ns = aa_prepare_namespace(e.ns_name);
+ if (IS_ERR(ns)) {
+ sa.base.info = "failed to prepare namespace";
+ sa.base.error = PTR_ERR(ns);
+ goto fail;
+ }
+ /* profiles are currently loaded flat with fqnames */
+ sa.name = profile->fqname;
+
+ write_lock(&ns->base.lock);
+
+ common = __aa_find_parent_by_fqname(ns, sa.name);
+ if (!common) {
+ sa.base.info = "parent does not exist";
+ sa.base.error = -ENOENT;
+ goto fail2;
+ }
+
+ if (common != &ns->base)
+ profile->parent = aa_get_profile((struct aa_profile *)common);
+
+ if (__aa_find_profile(&common->profiles, profile->base.name)) {
+ /* A profile with this name exists already. */
+ sa.base.info = "profile already exists";
+ sa.base.error = -EEXIST;
+ goto fail2;
+ }
+ profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ profile->ns = aa_get_namespace(ns);
+
+ __aa_add_profile(common, profile);
+ write_unlock(&ns->base.lock);
+
+ aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ return size;
+
+fail2:
+ write_unlock(&ns->base.lock);
+
+fail:
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(profile);
+ return error;
+}
+
+/**
+ * aa_interface_replace_profiles - replace profile(s) on the profile list
+ * @udata: serialized data stream
+ * @size: size of the serialized data stream
+ *
+ * unpack and replace a profile on the profile list and uses of that profile
+ * by any aa_task_context. If the profile does not exist on the profile list
+ * it is added. Return %0 or error.
+ */
+ssize_t aa_interface_replace_profiles(void *udata, size_t size)
+{
+ struct aa_policy_common *common;
+ struct aa_profile *old_profile = NULL, *new_profile;
+ struct aa_namespace *ns;
+ struct aa_ext e = {
+ .start = udata,
+ .end = udata + size,
+ .pos = udata,
+ .ns_name = NULL
+ };
+ ssize_t error;
+ struct aa_audit_iface sa;
+ aa_audit_init(&sa, "profile_replace", &e);
+
+ if (aa_g_lock_policy)
+ return -EACCES;
+
+ error = aa_verify_header(&e, &sa);
+ if (error)
+ return error;
+
+ new_profile = aa_unpack_profile(&e, &sa);
+ if (IS_ERR(new_profile))
+ return PTR_ERR(new_profile);
+
+ sa.name2 = e.ns_name;
+ ns = aa_prepare_namespace(e.ns_name);
+ if (!ns) {
+ sa.base.info = "failed to prepare namespace";
+ sa.base.error = -ENOMEM;
+ goto fail;
+ }
+
+ sa.name = new_profile->fqname;
+
+ write_lock(&ns->base.lock);
+ common = __aa_find_parent_by_fqname(ns, sa.name);
+
+ if (!common) {
+ sa.base.info = "parent does not exist";
+ sa.base.error = -ENOENT;
+ goto fail2;
+ }
+
+ if (common != &ns->base)
+ new_profile->parent = aa_get_profile((struct aa_profile *)
+ common);
+
+ old_profile = __aa_find_profile(&common->profiles,
+ new_profile->base.name);
+ aa_get_profile(old_profile);
+ if (old_profile && old_profile->flags & PFLAG_IMMUTABLE) {
+ sa.base.info = "cannot replace immutible profile";
+ sa.base.error = -EPERM;
+ goto fail2;
+ } else if (old_profile) {
+ struct aa_profile *profile, *tmp;
+ list_for_each_entry_safe(profile, tmp,
+ &old_profile->base.profiles,
+ base.list) {
+ aa_put_profile(profile->parent);
+ list_del(&profile->base.list);
+ profile->parent = aa_get_profile(new_profile);
+ list_add(&profile->base.list,
+ &new_profile->base.profiles);
+ }
+ __aa_replace_profile(old_profile, new_profile);
+ new_profile->sid = old_profile->sid;
+ } else {
+ __aa_add_profile(common, new_profile);
+ new_profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ }
+
+ new_profile->ns = aa_get_namespace(ns);
+
+ write_unlock(&ns->base.lock);
+
+ if (!old_profile)
+ sa.base.operation = "profile_load";
+
+ aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(old_profile);
+ return size;
+
+fail2:
+ write_unlock(&ns->base.lock);
+fail:
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(old_profile);
+ aa_put_profile(new_profile);
+ return error;
+}
+
+/**
+ * aa_interface_remove_profiles - remove profile(s) from the system
+ * @name: name of the profile to remove
+ * @size: size of the name
+ *
+ * remove a profile from the profile list and all aa_task_context references
+ * to said profile.
+ * NOTE: removing confinement does not restore rlimits to preconfinemnet values
+ */
+ssize_t aa_interface_remove_profiles(char *name, size_t size)
+{
+ struct aa_namespace *ns;
+ struct aa_profile *profile;
+ struct aa_audit_iface sa;
+ aa_audit_init(&sa, "profile_remove", NULL);
+
+ if (aa_g_lock_policy)
+ return -EACCES;
+
+ write_lock(&ns_list_lock);
+ if (name[0] == ':') {
+ char *ns_name;
+ name = aa_split_name_from_ns(name, &ns_name);
+ ns = __aa_find_namespace(&ns_list, ns_name);
+ } else {
+ ns = aa_get_namespace(default_namespace);
+ }
+
+ if (!ns) {
+ sa.base.info = "failed: namespace does not exist";
+ goto fail_ns_list_lock;
+ }
+
+ sa.name2 = ns->base.name;
+ write_lock(&ns->base.lock);
+ if (!name) {
+ /* remove namespace */
+ if (ns == default_namespace)
+ __aa_profile_list_release(&ns->base.profiles);
+ else
+ __aa_remove_namespace(ns);
+ } else {
+ /* remove profile */
+ profile = __aa_find_profile_by_fqname(ns, name);
+ if (!profile) {
+ sa.name = name;
+ sa.base.info = "failed: profile does not exist";
+ goto fail_ns_lock;
+ }
+ sa.name = profile->fqname;
+ __aa_profile_list_release(&profile->base.profiles);
+ __aa_remove_profile(profile, profile->ns->unconfined);
+ }
+ write_unlock(&ns->base.lock);
+ write_unlock(&ns_list_lock);
+
+ aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ return size;
+
+fail_ns_lock:
+ write_unlock(&ns->base.lock);
+
+fail_ns_list_lock:
+ write_unlock(&ns_list_lock);
+ aa_audit_iface(&sa);
+ return -ENOENT;
+}
--
1.6.3.3

2009-11-03 23:50:07

by John Johansen

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

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

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

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

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

diff --git a/security/apparmor/apparmorfs-24.c b/security/apparmor/apparmorfs-24.c
new file mode 100644
index 0000000..7fe9c56
--- /dev/null
+++ b/security/apparmor/apparmorfs-24.c
@@ -0,0 +1,180 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/secrutiy/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ *
+ * This file contain functions providing an interface for <= AppArmor 2.4
+ * compatibility. It is dependent on CONFIG_SECURITY_APPARMOR_COMPAT_24
+ * being set (see Makefile).
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+
+static struct aa_profile *next_profile(struct aa_profile *profile)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = profile->ns;
+
+ if (!list_empty(&profile->base.profiles))
+ return list_first_entry(&profile->base.profiles,
+ struct aa_profile, base.list);
+
+ parent = profile->parent;
+ while (parent) {
+ list_for_each_entry_continue(profile, &parent->base.profiles,
+ base.list)
+ return profile;
+ profile = parent;
+ parent = parent->parent;
+ }
+
+ list_for_each_entry_continue(profile, &ns->base.profiles, base.list)
+ return profile;
+
+ read_unlock(&ns->base.lock);
+ list_for_each_entry_continue(ns, &ns_list, base.list) {
+ read_lock(&ns->base.lock);
+ return list_first_entry(&ns->base.profiles, struct aa_profile,
+ base.list);
+ read_unlock(&ns->base.lock);
+ }
+ return NULL;
+}
+
+static void *p_start(struct seq_file *f, loff_t *pos) __acquires(ns_list_lock)
+{
+ struct aa_namespace *ns;
+ loff_t l = *pos;
+
+ read_lock(&ns_list_lock);
+ if (!list_empty(&ns_list)) {
+ struct aa_profile *profile = NULL;
+ ns = list_first_entry(&ns_list, typeof(*ns), base.list);
+ read_lock(&ns->base.lock);
+ if (!list_empty(&ns->base.profiles)) {
+ profile = list_first_entry(&ns->base.profiles,
+ typeof(*profile), base.list);
+ for (; profile && l > 0; l--)
+ profile = next_profile(profile);
+ return profile;
+ } else
+ read_unlock(&ns->base.lock);
+ }
+ return NULL;
+}
+
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+
+ (*pos)++;
+ profile = next_profile(profile);
+
+ return profile;
+}
+
+static void p_stop(struct seq_file *f, void *p) __releases(ns_list_lock)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+
+ if (profile)
+ read_unlock(&profile->ns->base.lock);
+ read_unlock(&ns_list_lock);
+}
+
+static void print_name(struct seq_file *f, struct aa_profile *profile)
+{
+ if (profile->parent) {
+ print_name(f, profile->parent);
+ seq_printf(f, "//");
+ }
+ seq_printf(f, "%s", profile->base.name);
+}
+
+static int seq_show_profile(struct seq_file *f, void *p)
+{
+ struct aa_profile *profile = (struct aa_profile *)p;
+
+ if (profile->ns != default_namespace)
+ seq_printf(f, ":%s:", profile->ns->base.name);
+ print_name(f, profile);
+ seq_printf(f, " (%s)\n",
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
+
+ return 0;
+}
+
+/* Used in apparmorfs.c */
+static const struct seq_operations aa_fs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
+
+static int aa_profiles_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &aa_fs_profiles_op);
+}
+
+static int aa_profiles_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+const struct file_operations aa_fs_profiles_fops = {
+ .open = aa_profiles_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_profiles_release,
+};
+
+/* apparmor/matching */
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char matching[] = "pattern=aadfa audit perms=crwxamlk/ "
+ "user::other";
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ sizeof(matching) - 1);
+}
+
+const struct file_operations aa_fs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+/* apparmor/features */
+static ssize_t aa_features_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char features[] = "file=3.1 capability=2.0 network=1.0 "
+ "change_hat=1.5 change_profile=1.1 " "aanamespaces=1.1 rlimit=1.1";
+
+ return simple_read_from_buffer(buf, size, ppos, features,
+ sizeof(features) - 1);
+}
+
+const struct file_operations aa_fs_features_fops = {
+ .read = aa_features_read,
+};
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
new file mode 100644
index 0000000..bb4a826
--- /dev/null
+++ b/security/apparmor/apparmorfs.c
@@ -0,0 +1,242 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /sys/kernel/security/apparmor interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/namei.h>
+
+#include "include/apparmor.h"
+#include "include/apparmorfs.h"
+#include "include/audit.h"
+#include "include/context.h"
+#include "include/policy.h"
+#include "include/policy_unpack.h"
+
+static char *aa_simple_write_to_buffer(const char __user *userbuf,
+ size_t alloc_size, size_t copy_size,
+ loff_t *pos, const char *operation)
+{
+ const struct cred *cred;
+ struct aa_profile *profile;
+ char *data;
+
+ if (*pos != 0) {
+ /* only writes from pos 0, that is complete writes */
+ data = ERR_PTR(-ESPIPE);
+ goto out;
+ }
+
+ /*
+ * Don't allow confined processes to load/replace/remove profiles.
+ * No sane person would add rules allowing this to a profile
+ * but we enforce the restriction anyways.
+ */
+ cred = aa_current_policy(&profile);
+ if (profile) {
+ struct aa_audit sa = {
+ .operation = operation,
+ .gfp_mask = GFP_KERNEL,
+ .error = -EACCES,
+ };
+ data = ERR_PTR(aa_audit(AUDIT_APPARMOR_DENIED, profile, &sa,
+ NULL));
+ goto out;
+ }
+
+ data = vmalloc(alloc_size);
+ if (data == NULL) {
+ data = ERR_PTR(-ENOMEM);
+ goto out;
+ }
+
+ if (copy_from_user(data, userbuf, copy_size)) {
+ vfree(data);
+ data = ERR_PTR(-EFAULT);
+ goto out;
+ }
+
+out:
+ return data;
+}
+
+/* apparmor/.load */
+static ssize_t aa_profile_load(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ char *data;
+ ssize_t error;
+
+ data = aa_simple_write_to_buffer(buf, size, size, pos, "profile_load");
+
+ error = PTR_ERR(data);
+ if (!IS_ERR(data)) {
+ error = aa_interface_add_profiles(data, size);
+ vfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_profile_load = {
+ .write = aa_profile_load
+};
+
+/* apparmor/.replace */
+static ssize_t aa_profile_replace(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ char *data;
+ ssize_t error;
+
+ data = aa_simple_write_to_buffer(buf, size, size, pos,
+ "profile_replace");
+ error = PTR_ERR(data);
+ if (!IS_ERR(data)) {
+ error = aa_interface_replace_profiles(data, size);
+ vfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_profile_replace = {
+ .write = aa_profile_replace
+};
+
+/* apparmor/.remove */
+static ssize_t aa_profile_remove(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos)
+{
+ char *data;
+ ssize_t error;
+
+ /*
+ * aa_remove_profile needs a null terminated string so 1 extra
+ * byte is allocated and the copied data is null terminated.
+ */
+ data = aa_simple_write_to_buffer(buf, size + 1, size, pos,
+ "profile_remove");
+
+ error = PTR_ERR(data);
+ if (!IS_ERR(data)) {
+ data[size] = 0;
+ error = aa_interface_remove_profiles(data, size);
+ vfree(data);
+ }
+
+ return error;
+}
+
+static const struct file_operations aa_fs_profile_remove = {
+ .write = aa_profile_remove
+};
+
+static struct dentry *aa_fs_dentry;
+struct dentry *aa_fs_null;
+struct vfsmount *aa_fs_mnt;
+
+static void aafs_remove(const char *name)
+{
+ struct dentry *dentry;
+
+ dentry = lookup_one_len(name, aa_fs_dentry, strlen(name));
+ if (!IS_ERR(dentry)) {
+ securityfs_remove(dentry);
+ dput(dentry);
+ }
+}
+
+static int aafs_create(const char *name, int mask,
+ const struct file_operations *fops)
+{
+ struct dentry *dentry;
+
+ dentry = securityfs_create_file(name, S_IFREG | mask, aa_fs_dentry,
+ NULL, fops);
+
+ return IS_ERR(dentry) ? PTR_ERR(dentry) : 0;
+}
+
+void aa_destroy_aafs(void)
+{
+ if (aa_fs_dentry) {
+ aafs_remove(".remove");
+ aafs_remove(".replace");
+ aafs_remove(".load");
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ aafs_remove("matching");
+ aafs_remove("features");
+ aafs_remove("profiles");
+#endif
+ securityfs_remove(aa_fs_dentry);
+ aa_fs_dentry = NULL;
+ }
+}
+
+int aa_create_aafs(void)
+{
+ int error;
+
+ if (!apparmor_initialized)
+ return 0;
+
+ if (aa_fs_dentry) {
+ AA_ERROR("%s: AppArmor securityfs already exists\n", __func__);
+ return -EEXIST;
+ }
+
+ aa_fs_dentry = securityfs_create_dir("apparmor", NULL);
+ if (IS_ERR(aa_fs_dentry)) {
+ error = PTR_ERR(aa_fs_dentry);
+ aa_fs_dentry = NULL;
+ goto error;
+ }
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+ error = aafs_create("profiles", 0440, &aa_fs_profiles_fops);
+ if (error)
+ goto error;
+ error = aafs_create("matching", 0444, &aa_fs_matching_fops);
+ if (error)
+ goto error;
+ error = aafs_create("features", 0444, &aa_fs_features_fops);
+ if (error)
+ goto error;
+#endif
+ error = aafs_create(".load", 0640, &aa_fs_profile_load);
+ if (error)
+ goto error;
+ error = aafs_create(".replace", 0640, &aa_fs_profile_replace);
+ if (error)
+ goto error;
+ error = aafs_create(".remove", 0640, &aa_fs_profile_remove);
+ if (error)
+ goto error;
+
+ /* TODO: add support for apparmorfs_null and apparmorfs_mnt */
+
+ /* Report that AppArmor fs is enabled */
+ aa_info_message("AppArmor Filesystem Enabled");
+ return 0;
+
+error:
+ aa_destroy_aafs();
+ AA_ERROR("Error creating AppArmor securityfs\n");
+ apparmor_disable();
+ return error;
+}
+
+fs_initcall(aa_create_aafs);
diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
new file mode 100644
index 0000000..177b35b
--- /dev/null
+++ b/security/apparmor/include/apparmorfs.h
@@ -0,0 +1,29 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor filesystem definitions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __AA_APPARMORFS_H
+#define __AA_APPARMORFS_H
+
+extern struct dentry *aa_fs_null;
+extern struct vfsmount *aa_fs_mnt;
+
+extern void aa_destroy_aafs(void);
+
+#ifdef CONFIG_SECURITY_APPARMOR_COMPAT_24
+extern const struct file_operations aa_fs_profiles_fops;
+extern const struct file_operations aa_fs_matching_fops;
+extern const struct file_operations aa_fs_features_fops;
+#endif
+
+#endif /* __AA_APPARMORFS_H */
diff --git a/security/apparmor/include/procattr.h b/security/apparmor/include/procattr.h
new file mode 100644
index 0000000..70f4c8a
--- /dev/null
+++ b/security/apparmor/include/procattr.h
@@ -0,0 +1,26 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /proc/<pid>/attr/ interface function defintions.
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __AA_PROCATTR_H
+#define __AA_PROCATTR_H
+
+#define AA_DO_TEST 1
+
+int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile,
+ char **string);
+int aa_setprocattr_changehat(char *args, int test);
+int aa_setprocattr_changeprofile(char *args, int onexec, int test);
+int aa_setprocattr_permipc(char *args);
+
+#endif /* __AA_PROCATTR_H */
diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c
new file mode 100644
index 0000000..e3510ea
--- /dev/null
+++ b/security/apparmor/procattr.c
@@ -0,0 +1,115 @@
+/*
+ * AppArmor security module
+ *
+ * This file contains AppArmor /proc/<pid>/attr/ interface functions
+ *
+ * Copyright (C) 1998-2008 Novell/SUSE
+ * Copyright 2009 Canonical Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#include "include/apparmor.h"
+#include "include/policy.h"
+#include "include/domain.h"
+
+int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile,
+ char **string)
+{
+ char *str;
+ int len = 0;
+
+ if (profile) {
+ int mode_len, name_len, ns_len = 0;
+ const char *mode_str = profile_mode_names[profile->mode];
+ char *s;
+
+ mode_len = strlen(mode_str) + 3; /* _(mode_str)\n */
+ name_len = strlen(profile->fqname);
+ if (ns != default_namespace)
+ ns_len = strlen(ns->base.name) + 3;
+ len = mode_len + ns_len + name_len + 1;
+ s = str = kmalloc(len + 1, GFP_ATOMIC);
+ if (!str)
+ return -ENOMEM;
+
+ if (ns_len) {
+ sprintf(s, "%s://", ns->base.name);
+ s += ns_len;
+ }
+ memcpy(s, profile->fqname, name_len);
+ s += name_len;
+ sprintf(s, " (%s)\n", mode_str);
+ } else {
+ const char unconfined_str[] = "unconfined\n";
+
+ len = sizeof(unconfined_str) - 1;
+ if (ns != default_namespace)
+ len += strlen(ns->base.name) + 3;
+
+ str = kmalloc(len + 1, GFP_ATOMIC);
+ if (!str)
+ return -ENOMEM;
+
+ if (ns != default_namespace)
+ sprintf(str, "%s://%s", ns->base.name, unconfined_str);
+ else
+ memcpy(str, unconfined_str, len);
+ }
+ *string = str;
+
+ return len;
+}
+
+static char *split_token_from_name(const char *op, char *args, u64 * token)
+{
+ char *name;
+
+ *token = simple_strtoull(args, &name, 16);
+ if ((name == args) || *name != '^') {
+ AA_ERROR("%s: Invalid input '%s'", op, args);
+ return ERR_PTR(-EINVAL);
+ }
+
+ name++; /* skip ^ */
+ if (!*name)
+ name = NULL;
+ return name;
+}
+
+int aa_setprocattr_changehat(char *args, int test)
+{
+ char *hat;
+ u64 token;
+
+ hat = split_token_from_name("change_hat", args, &token);
+ if (IS_ERR(hat))
+ return PTR_ERR(hat);
+
+ if (!hat && !token) {
+ AA_ERROR("change_hat: Invalid input, NULL hat and NULL magic");
+ return -EINVAL;
+ }
+
+ AA_DEBUG("%s: Magic 0x%llx Hat '%s'\n",
+ __func__, token, hat ? hat : NULL);
+
+ return aa_change_hat(hat, token, test);
+}
+
+int aa_setprocattr_changeprofile(char *args, int onexec, int test)
+{
+ char *name, *ns_name;
+
+ name = aa_split_name_from_ns(args, &ns_name);
+ return aa_change_profile(ns_name, name, onexec, test);
+}
+
+int aa_setprocattr_permipc(char *args)
+{
+ /* TODO: add ipc permission querying */
+ return -ENOTSUPP;
+}
--
1.6.3.3

2009-11-03 23:51:24

by John Johansen

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

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

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

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

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

2009-11-03 23:50:16

by John Johansen

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

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

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

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

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

More advanced network rules are a wip.

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

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

set capability sys_admin,

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

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

The current implementation does not set the task capabilities masks.

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/capability.c | 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

2009-11-03 23:50:26

by John Johansen

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

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

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

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

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

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

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

2009-11-03 23:50:22

by John Johansen

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

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

Signed-off-by: John Johansen <[email protected]>
---
security/apparmor/lsm.c | 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

2009-11-03 23:50:44

by John Johansen

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

Kconfig and Makefiles to enable configuration and building of AppArmor.

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

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

source security/integrity/ima/Kconfig

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

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

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

2009-11-04 05:39:09

by Tetsuo Handa

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

Hello.

Some random topics I noticed. I need to use lxr for further review.

John Johansen wrote:
[01/12]
+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
+ */
+

Yes, we know. But .... isn't there a race window that the file is unlink()ed
between __d_path() and d_unlinked(path->dentry) ? Holding dcache_lock prevents
this race?

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



[02/12]
+static int aa_audit_base(int type, struct aa_profile *profile,
+ struct aa_audit *sa, struct audit_context *audit_cxt,
+ void (*cb) (struct audit_buffer *, void *))
+{
+ struct audit_buffer *ab = NULL;

You can add

struct task_struct *task = sa->task ? sa->task : current;

and replace subsequent "sa->task ? ... : ...".

+
+ if (profile && PROFILE_KILL(profile) && type == AUDIT_APPARMOR_DENIED)
+ type = AUDIT_APPARMOR_KILL;
+
+ ab = audit_log_start(audit_cxt, sa->gfp_mask, type);
+
+ if (!ab) {
+ AA_ERROR("(%d) Unable to log event of type (%d)\n",
+ -ENOMEM, type);

Don't you want

if (type == AUDIT_APPARMOR_KILL)
(void)send_sig_info(SIGKILL, NULL,
sa->task ? sa->task : current);

so that process is killed when audit_log_start() failed?

+ /* don't fail operations in complain mode even if logging
+ * fails */
+ return type == AUDIT_APPARMOR_ALLOWED ? 0 : -ENOMEM;
+ }
+
+ if (aa_g_audit_header)
+ audit_log_format(ab, "type=%s ",
+ aa_audit_type[type - AUDIT_APPARMOR_AUDIT]);
+
+ if (sa->operation)
+ audit_log_format(ab, "operation=\"%s\"", sa->operation);
+
+ if (sa->info) {
+ audit_log_format(ab, " info=\"%s\"", sa->info);
+ if (sa->error)
+ audit_log_format(ab, " error=%d", sa->error);
+ }
+
+ audit_log_format(ab, " pid=%d",
+ sa->task ? sa->task->pid : current->pid);
+
+ if (profile) {
+ pid_t pid = sa->task ? sa->task->real_parent->pid :
+ current->real_parent->pid;
+ audit_log_format(ab, " parent=%d", pid);
+ audit_log_format(ab, " profile=");
+ audit_log_untrustedstring(ab, profile->fqname);
+
+ if (profile->ns != default_namespace) {
+ audit_log_format(ab, " namespace=");
+ audit_log_untrustedstring(ab, profile->ns->base.name);
+ }
+ }
+
+ if (cb)
+ cb(ab, sa);
+
+ audit_log_end(ab);

Not checking -ENOMEM failures for audit_log_format() etc. might cause
partial audit logs. Is it OK?

+
+ if (type == AUDIT_APPARMOR_KILL)
+ (void)send_sig_info(SIGKILL, NULL,
+ sa->task ? sa->task : current);
+
+ return type == AUDIT_APPARMOR_ALLOWED ? 0 : sa->error;
+}



[03/12]
+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);
+}

Why not kzfree(cxt); instead of memset() + kfree() ?



[04/12]
+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;
+}

Any reason to call aa_put_namespace(default_namespace); with a lock and
without a lock?



[06/12]
+static int unpack_dynstring(struct aa_ext *e, char **string, const char *name)
+{
+ char *tmp;
+ void *pos = e->pos;
+ int res = unpack_string(e, &tmp, name);
+ *string = NULL;
+
+ if (!res)
+ return res;

return 0; ?

+
+ *string = kstrdup(tmp, GFP_KERNEL);
+ if (!*string) {
+ e->pos = pos;
+ return 0;
+ }
+
+ return res;
+}


+static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
+{
+ void *pos = e->pos;
+
+ /* exec table is optional */
+ if (unpack_nameX(e, AA_STRUCT, "xtable")) {
+ int i, size;
+
+ size = unpack_array(e, NULL);
+ /* currently 4 exec bits and entries 0-3 are reserved iupcx */
+ if (size > 16 - 4)
+ goto fail;
+ profile->file.trans.table = kzalloc(sizeof(char *) * size,
+ GFP_KERNEL);
+ if (!profile->file.trans.table)
+ goto fail;
+

profile->file.trans.size = size;

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

This assignment is too late to pass the size to aa_free_domain_entries().

+ }
+ return 1;
+
+fail:
+ e->pos = pos;
+ return 0;
+}

+ssize_t aa_interface_add_profiles(void *data, size_t size)
+{
+ struct aa_profile *profile;
+ struct aa_namespace *ns = NULL;
+ struct aa_policy_common *common;
+ struct aa_ext e = {
+ .start = data,
+ .end = data + size,
+ .pos = data,
+ .ns_name = NULL
+ };
+ ssize_t error;
+ struct aa_audit_iface sa;
+ aa_audit_init(&sa, "profile_load", &e);
+
+ error = aa_verify_header(&e, &sa);
+ if (error)
+ return error;
+
+ profile = aa_unpack_profile(&e, &sa);
+ if (IS_ERR(profile))
+ return PTR_ERR(profile);
+
+ sa.name2 = e.ns_name;
+ ns = aa_prepare_namespace(e.ns_name);
+ if (IS_ERR(ns)) {
+ sa.base.info = "failed to prepare namespace";
+ sa.base.error = PTR_ERR(ns);
+ goto fail;
+ }
+ /* profiles are currently loaded flat with fqnames */
+ sa.name = profile->fqname;
+
+ write_lock(&ns->base.lock);
+
+ common = __aa_find_parent_by_fqname(ns, sa.name);
+ if (!common) {
+ sa.base.info = "parent does not exist";
+ sa.base.error = -ENOENT;
+ goto fail2;
+ }
+
+ if (common != &ns->base)
+ profile->parent = aa_get_profile((struct aa_profile *)common);
+
+ if (__aa_find_profile(&common->profiles, profile->base.name)) {
+ /* A profile with this name exists already. */
+ sa.base.info = "profile already exists";
+ sa.base.error = -EEXIST;

Don't you need to call aa_put_profile(common) if common != &ns->base ?

+ goto fail2;
+ }
+ profile->sid = aa_alloc_sid(AA_ALLOC_SYS_SID);
+ profile->ns = aa_get_namespace(ns);
+
+ __aa_add_profile(common, profile);
+ write_unlock(&ns->base.lock);
+
+ aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ return size;
+
+fail2:
+ write_unlock(&ns->base.lock);
+
+fail:
+ error = aa_audit_iface(&sa);
+ aa_put_namespace(ns);
+ aa_put_profile(profile);
+ return error;
+}



[07/12]
+static struct aa_profile *next_profile(struct aa_profile *profile)
+{
+ struct aa_profile *parent;
+ struct aa_namespace *ns = profile->ns;
+
+ if (!list_empty(&profile->base.profiles))
+ return list_first_entry(&profile->base.profiles,
+ struct aa_profile, base.list);
+
+ parent = profile->parent;
+ while (parent) {
+ list_for_each_entry_continue(profile, &parent->base.profiles,
+ base.list)
+ return profile;
+ profile = parent;
+ parent = parent->parent;
+ }
+
+ list_for_each_entry_continue(profile, &ns->base.profiles, base.list)
+ return profile;
+
+ read_unlock(&ns->base.lock);
+ list_for_each_entry_continue(ns, &ns_list, base.list) {
+ read_lock(&ns->base.lock);
+ return list_first_entry(&ns->base.profiles, struct aa_profile,
+ base.list);
+ read_unlock(&ns->base.lock);

This read_unlock() unreachable?

+ }
+ return NULL;
+}

+int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile,
+ char **string)
+{
+ char *str;
+ int len = 0;
+
+ if (profile) {
+ int mode_len, name_len, ns_len = 0;
+ const char *mode_str = profile_mode_names[profile->mode];
+ char *s;
+
+ mode_len = strlen(mode_str) + 3; /* _(mode_str)\n */
+ name_len = strlen(profile->fqname);
+ if (ns != default_namespace)
+ ns_len = strlen(ns->base.name) + 3;
+ len = mode_len + ns_len + name_len + 1;
+ s = str = kmalloc(len + 1, GFP_ATOMIC);
+ if (!str)
+ return -ENOMEM;
+
+ if (ns_len) {
+ sprintf(s, "%s://", ns->base.name);
+ s += ns_len;
+ }
+ memcpy(s, profile->fqname, name_len);
+ s += name_len;
+ sprintf(s, " (%s)\n", mode_str);
+ } else {
+ const char unconfined_str[] = "unconfined\n";
+
+ len = sizeof(unconfined_str) - 1;
+ if (ns != default_namespace)
+ len += strlen(ns->base.name) + 3;
+
+ str = kmalloc(len + 1, GFP_ATOMIC);
+ if (!str)
+ return -ENOMEM;
+
+ if (ns != default_namespace)
+ sprintf(str, "%s://%s", ns->base.name, unconfined_str);
+ else
+ memcpy(str, unconfined_str, len);

You need to copy one more byte to make str \0-terminated.

memcpy(str, unconfined_str, len + 1);

+ }
+ *string = str;
+
+ return len;
+}



[10/12]
+static int aa_may_change_ptraced_domain(struct task_struct *task,
+ struct aa_profile *to_profile)
+{
+ struct task_struct *tracer;
+ struct cred *cred = NULL;
+ struct aa_profile *tracerp = NULL;
+ int error = 0;
+
+ rcu_read_lock();
+ tracer = tracehook_tracer_task(task);
+ if (tracer)
+ cred = aa_get_task_policy(tracer, &tracerp);
+ rcu_read_unlock();
+
+ if (!tracerp)

Don't you need to call put_cred(cred); because aa_get_task_policy() calls
get_task_cred() but aa_cred_policy() may set tracerp to NULL ?

+ return error;
+
+ error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH);
+ put_cred(cred);
+
+ return error;
+}



[11/12]
+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,
+};

Don't you need to define ".cred_alloc_blank" and ".cred_transfer" ?

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

You can add __init to this function.

2009-11-05 05:10:46

by John Johansen

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

Tetsuo Handa wrote:
> Hello.
>
> Some random topics I noticed. I need to use lxr for further review.
>
> John Johansen wrote:
> [01/12]
> +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
> + */
> +
>
> Yes, we know. But .... isn't there a race window that the file is unlink()ed
> between __d_path() and d_unlinked(path->dentry) ? Holding dcache_lock prevents
> this race?
>
bleah, no not in general. For the creation case (it was showing up in mknod on
nfs) I think it is okay but I need to go back and double check that. The other
case however needs fixed as we currently can't avoid mediating deleted paths in
some cases. In general I would like to work towards eliminating this case.

>
>
> [02/12]
> +static int aa_audit_base(int type, struct aa_profile *profile,
> + struct aa_audit *sa, struct audit_context *audit_cxt,
> + void (*cb) (struct audit_buffer *, void *))
> +{
> + struct audit_buffer *ab = NULL;
>
> You can add
>
> struct task_struct *task = sa->task ? sa->task : current;
>
> and replace subsequent "sa->task ? ... : ...".
>
yep, that is a nice little cleanup


> +
> + if (profile && PROFILE_KILL(profile) && type == AUDIT_APPARMOR_DENIED)
> + type = AUDIT_APPARMOR_KILL;
> +
> + ab = audit_log_start(audit_cxt, sa->gfp_mask, type);
> +
> + if (!ab) {
> + AA_ERROR("(%d) Unable to log event of type (%d)\n",
> + -ENOMEM, type);
>
> Don't you want
>
> if (type == AUDIT_APPARMOR_KILL)
> (void)send_sig_info(SIGKILL, NULL,
> sa->task ? sa->task : current);
>
> so that process is killed when audit_log_start() failed?
>
yep another good catch


> + audit_log_format(ab, " pid=%d",
> + sa->task ? sa->task->pid : current->pid);
> +
> + if (profile) {
> + pid_t pid = sa->task ? sa->task->real_parent->pid :
> + current->real_parent->pid;
> + audit_log_format(ab, " parent=%d", pid);
> + audit_log_format(ab, " profile=");
> + audit_log_untrustedstring(ab, profile->fqname);
> +
> + if (profile->ns != default_namespace) {
> + audit_log_format(ab, " namespace=");
> + audit_log_untrustedstring(ab, profile->ns->base.name);
> + }
> + }
> +
> + if (cb)
> + cb(ab, sa);
> +
> + audit_log_end(ab);
>
> Not checking -ENOMEM failures for audit_log_format() etc. might cause
> partial audit logs. Is it OK?
>
That would be dependent on the situation, currently we have been tolerant
of partial logs, but the plan has been to add flags to specify if audit
failures should be tolerated or not.

So this definitely needs updated.

>
> [03/12]
> +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);
> +}
>
> Why not kzfree(cxt); instead of memset() + kfree() ?
>
I missed it somehow, must have been temporary blindness ;)

>
> [04/12]
> +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;
> +}
>
> Any reason to call aa_put_namespace(default_namespace); with a lock and
> without a lock?
>
>
hehe, no. That does look confusing doesn't it, basically the list holds a
reference and the variable holds a reference. The put inside the locks
was dropping the list ref, I think I actually had that in a macro at one point.

the put_namespace can certainly be pulled out of the lock

>
> [06/12]
> +static int unpack_dynstring(struct aa_ext *e, char **string, const char *name)
> +{
> + char *tmp;
> + void *pos = e->pos;
> + int res = unpack_string(e, &tmp, name);
> + *string = NULL;
> +
> + if (!res)
> + return res;
>
> return 0; ?
>
smae thing but yeah that would be better


> +static int aa_unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
> +{
> + void *pos = e->pos;
> +
> + /* exec table is optional */
> + if (unpack_nameX(e, AA_STRUCT, "xtable")) {
> + int i, size;
> +
> + size = unpack_array(e, NULL);
> + /* currently 4 exec bits and entries 0-3 are reserved iupcx */
> + if (size > 16 - 4)
> + goto fail;
> + profile->file.trans.table = kzalloc(sizeof(char *) * size,
> + GFP_KERNEL);
> + if (!profile->file.trans.table)
> + goto fail;
> +
>
> profile->file.trans.size = size;
>
> + for (i = 0; i < size; i++) {
> + char *tmp;
> + if (!unpack_dynstring(e, &tmp, NULL))
> + goto fail;
> + /*
> + * note: strings beginning with a : have an embedded
> + * \0 seperating the profile ns name from the profile
> + * name
> + */
> + profile->file.trans.table[i] = tmp;
> + }
> + if (!unpack_nameX(e, AA_ARRAYEND, NULL))
> + goto fail;
> + if (!unpack_nameX(e, AA_STRUCTEND, NULL))
> + goto fail;
> + profile->file.trans.size = size;
>
> This assignment is too late to pass the size to aa_free_domain_entries().
>
yikes, yep thanks for catching that.

>
> +ssize_t aa_interface_add_profiles(void *data, size_t size)
> +{
> + struct aa_profile *profile;
> + struct aa_namespace *ns = NULL;
> + struct aa_policy_common *common;
> + struct aa_ext e = {
> + .start = data,
> + .end = data + size,
> + .pos = data,
> + .ns_name = NULL
> + };
> + ssize_t error;
> + struct aa_audit_iface sa;
> + aa_audit_init(&sa, "profile_load", &e);
> +
> + error = aa_verify_header(&e, &sa);
> + if (error)
> + return error;
> +
> + profile = aa_unpack_profile(&e, &sa);
> + if (IS_ERR(profile))
> + return PTR_ERR(profile);
> +
> + sa.name2 = e.ns_name;
> + ns = aa_prepare_namespace(e.ns_name);
> + if (IS_ERR(ns)) {
> + sa.base.info = "failed to prepare namespace";
> + sa.base.error = PTR_ERR(ns);
> + goto fail;
> + }
> + /* profiles are currently loaded flat with fqnames */
> + sa.name = profile->fqname;
> +
> + write_lock(&ns->base.lock);
> +
> + common = __aa_find_parent_by_fqname(ns, sa.name);
> + if (!common) {
> + sa.base.info = "parent does not exist";
> + sa.base.error = -ENOENT;
> + goto fail2;
> + }
> +
> + if (common != &ns->base)
> + profile->parent = aa_get_profile((struct aa_profile *)common);
> +
> + if (__aa_find_profile(&common->profiles, profile->base.name)) {
> + /* A profile with this name exists already. */
> + sa.base.info = "profile already exists";
> + sa.base.error = -EEXIST;
>
> Don't you need to call aa_put_profile(common) if common != &ns->base ?
>
no,
__aa_find_parent_by_fqname doesn't increment the ref count so
the reference for common is actually held by profile->parent. When
the profile is put it will put the reference to common.

However not taking a reference on common could be considered playing fast and
lose with the ref count.

>
> [07/12]
> +static struct aa_profile *next_profile(struct aa_profile *profile)
> +{
> + struct aa_profile *parent;
> + struct aa_namespace *ns = profile->ns;
> +
> + if (!list_empty(&profile->base.profiles))
> + return list_first_entry(&profile->base.profiles,
> + struct aa_profile, base.list);
> +
> + parent = profile->parent;
> + while (parent) {
> + list_for_each_entry_continue(profile, &parent->base.profiles,
> + base.list)
> + return profile;
> + profile = parent;
> + parent = parent->parent;
> + }
> +
> + list_for_each_entry_continue(profile, &ns->base.profiles, base.list)
> + return profile;
> +
> + read_unlock(&ns->base.lock);
> + list_for_each_entry_continue(ns, &ns_list, base.list) {
> + read_lock(&ns->base.lock);
> + return list_first_entry(&ns->base.profiles, struct aa_profile,
> + base.list);
> + read_unlock(&ns->base.lock);
>
> This read_unlock() unreachable?
>
yep so it is, will drop it.


> + }
> + return NULL;
> +}
>
> +int aa_getprocattr(struct aa_namespace *ns, struct aa_profile *profile,
> + char **string)
> +{
> + char *str;
> + int len = 0;
> +
> + if (profile) {
> + int mode_len, name_len, ns_len = 0;
> + const char *mode_str = profile_mode_names[profile->mode];
> + char *s;
> +
> + mode_len = strlen(mode_str) + 3; /* _(mode_str)\n */
> + name_len = strlen(profile->fqname);
> + if (ns != default_namespace)
> + ns_len = strlen(ns->base.name) + 3;
> + len = mode_len + ns_len + name_len + 1;
> + s = str = kmalloc(len + 1, GFP_ATOMIC);
> + if (!str)
> + return -ENOMEM;
> +
> + if (ns_len) {
> + sprintf(s, "%s://", ns->base.name);
> + s += ns_len;
> + }
> + memcpy(s, profile->fqname, name_len);
> + s += name_len;
> + sprintf(s, " (%s)\n", mode_str);
> + } else {
> + const char unconfined_str[] = "unconfined\n";
> +
> + len = sizeof(unconfined_str) - 1;
> + if (ns != default_namespace)
> + len += strlen(ns->base.name) + 3;
> +
> + str = kmalloc(len + 1, GFP_ATOMIC);
> + if (!str)
> + return -ENOMEM;
> +
> + if (ns != default_namespace)
> + sprintf(str, "%s://%s", ns->base.name, unconfined_str);
> + else
> + memcpy(str, unconfined_str, len);
>
> You need to copy one more byte to make str \0-terminated.
>
interesting, looking back through some log not null terminating it was
actually deliberate, as it is treating the value as file contents not a
string. But it is not what we are doing above any more, thanks for point it
out.


> [10/12]
> +static int aa_may_change_ptraced_domain(struct task_struct *task,
> + struct aa_profile *to_profile)
> +{
> + struct task_struct *tracer;
> + struct cred *cred = NULL;
> + struct aa_profile *tracerp = NULL;
> + int error = 0;
> +
> + rcu_read_lock();
> + tracer = tracehook_tracer_task(task);
> + if (tracer)
> + cred = aa_get_task_policy(tracer, &tracerp);
> + rcu_read_unlock();
> +
> + if (!tracerp)
>
> Don't you need to call put_cred(cred); because aa_get_task_policy() calls
> get_task_cred() but aa_cred_policy() may set tracerp to NULL ?
>
indeed.



> +
> + .cred_free = apparmor_cred_free,
> + .cred_prepare = apparmor_cred_prepare,
> +
>
> Don't you need to define ".cred_alloc_blank" and ".cred_transfer" ?
>
hrmm, yes it seems I managed to drop that patch.

> +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;
> +}
>
> You can add __init to this function.
yep

thanks again Tetsuo


2009-11-05 05:49:28

by Tetsuo Handa

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

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.

2009-11-06 23:50:16

by John Johansen

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

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

2009-11-09 15:20:42

by Eric Paris

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

On Tue, Nov 3, 2009 at 6:48 PM, John Johansen
<[email protected]> wrote:
> AppArmor hooks to interface with the LSM, and module parameters and
> initialization.
>
> Signed-off-by: John Johansen <[email protected]>
> ---


> +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);
> +}

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

2009-11-09 15:37:24

by Eric Paris

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

On Tue, Nov 3, 2009 at 6:48 PM, John Johansen
<[email protected]> 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 <[email protected]>

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

2009-11-10 18:38:26

by John Johansen

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

Eric Paris wrote:
> On Tue, Nov 3, 2009 at 6:48 PM, John Johansen
> <[email protected]> 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 <[email protected]>
>
> 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 :)
>
>
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

2009-11-10 18:38:54

by John Johansen

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

Eric Paris wrote:
> On Tue, Nov 3, 2009 at 6:48 PM, John Johansen
> <[email protected]> wrote:
>> AppArmor hooks to interface with the LSM, and module parameters and
>> initialization.
>>
>> Signed-off-by: John Johansen <[email protected]>
>> ---
>
>
>> +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);
>> +}
>
> 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....
>
yep, thanks for pointing this out. I will look into it.

john