Discussion:
[RFC][PATCH 0/11] security: AppArmor - Overview
(too old to reply)
Tony Jones
2006-04-19 17:49:05 UTC
Permalink
Attached patches to include the AppArmor application security module in
the linux kernel.

An overview of AppArmor is available here
http://en.opensuse.org/Apparmor and a more detailed view here
http://en.opensuse.org/AppArmor_Detail A video of an overview and demo
of AppArmor is available here
ftp://ftp.belnet.be/pub/mirror/FOSDEM/FOSDEM2006-apparmor.avi

Overview
-----------
AppArmor is an LSM security enhancement for the Linux kernel. The
primary goal of AppArmor is to make it easy for a system administrator
to control application behavior, enforcing that the application has
access to only the files and POSIX.1e draft capabilities it requires to
do its job. AppArmor deliberately uses this simple access control model
to make it as easy as possible for the administrator to manage the
policy, because the worst security of all is that which is never
deployed because it was too hard.

AppArmor chooses which security policy to enforce for a process at
exec() time by the executable image's pathname, in conjunction with any
policy enforced for the currently running executable.

AppArmor mediates access to the file system using absolute path names
with shell-syntax wildcards, so that "/srv/htdocs/** r" grants read
access to all files in /srv/htdocs. AppArmor mediates access to POSIX.1e
Capabilities in that the process must both have e.g. "capability
net_bind_service" and intrinsically have that capability (usually by
being root) to be able to bind to privileged network ports. Thus a
confined process can not subvert AppArmor except as permitted by policy,
and can not access the file system except as permitted by the profile.

AppArmor is strictly monotonic to security: it only restricts privilege,
never enhancing privilege. So if you add AppArmor to a system, it only
becomes more secure or stays the same, the security policy will not add
vulnerabilities. Similarly, AppArmor is designed to be highly
transparent to applications: If you add AppArmor to a working system,
you have to develop AppArmor profiles, but you do not have to change
your applications. If you remove AppArmor from a running system, the
system continues to operate exactly as before, but without the AppArmor
security protections.

AppArmor is *not* intended to protect every aspect of the system from
every other aspect of the system: the intended usage is that only a
small fraction of all programs on a Linux system will have AppArmor
profiles. Rather, AppArmor is intended to protect the system against a
particular threat.

For instance, to secure a machine against network attack, all programs
that face the network should be profiled. If all open network ports lead
to AppArmor profiles, then there is no way for the network attacker to
attack the machine, except as controlled by AppArmor policy. As a
convenience, AppArmor includes a netstat wrapper that reports all
programs with open network ports and their AppArmor profile status.

AppArmor includes a training system so that a profile can be built by
exercising a program in "complain" mode where rules are not enforced but
violations are logged. User-level tools can then transform this log of
events into application security profiles by asking the user questions.
The profile generator is intelligent about not asking duplicate
questions, incrementally improving existing profiles, and suggesting
generalized alternatives to specific events, such as inserting * into
path names that appear to be library version numbers.

AppArmor has been split into two modules, the primary apparmor module
and a submodule that implements the necessary pathname matching
functions. The SuSE release of AppArmor uses a sub-module which supports
full shell pathname expansion syntax. This is achieved using a subset of
PCRE and limits on expression complexity at the userside compiler. It is
understood that this approach is not acceptable for mainline inclusion.
The version submitted here uses a simpler matching submodule that
implements literal and tailglob matches only. We plan on developing a
new submodule that will implement the missing functionality of the SuSE
release using the textsearch framework and a new bounded textsearch
algorithm acceptable for subsequent inclusion into the mainline kernel.

The features supported by the matching sub module are exposed into the
apparmor filesystem and read by the userspace parser which will prevent
unsupported policy from being loaded.

Without the use of this extended globbing module, AppArmor supports only
globs in the following form:

/path/to/files**

or:

/path/to/directory/**

Who Needs This?
-------------------
AppArmor is a core part of SUSE Linux. It has also been ported to
Slackware, Ubuntu, Gentoo, Red Hat, and Pardus Linux. AppArmor is not
"needed" but is desirable where ever an application hosted on Linux is
exposed to attack.

Patches
--------
The implementation has been broken down into 11 patches, with brief
descriptions here, and longer descriptions in each of the patch posts
that follow

1. apparmor_build.patch. Integrate into kbuild.
2. apparmor_headers.patch. Core headers.
3. apparmor_lsm.patch. LSM interface implementation.
4. apparmor_mediation.patch. Core access controls.
5. apparmor_fs.patch. AppArmor filesystem.
6. apparmor_interface.patch. Usersapce/kernelspace interface.
7. apparmor_misc.patch. Misc., including Capabilities and data
structure management.
8. apparmor_match.patch. Pathname matching submodule.
9. audit.patch. Integrate into audit subsystem.
10. dpath_flags.patch. Generate absolute path names.
11. namespace_sem.patch. Exports the namespace_sem semaphore.

The patches apply cleanly to 2.6.17-rc1 and -rc2.


Tests
------
The AppArmor team has a suite of functionality and stress tests
http://www.apparmor.org/

Bugs
----

1. The simple tail-glob pattern matching sub-module described above
needs to be replaced with a fully functional pattern matching
module that uses textsearch facilities as soon as possible.
2. AppArmor needs to re-construct the full path name of files to
perform initial validation. Some of the LSM hooks that we mediate
do not have vfsmount/nameidata passed. Our temporary workaround is
to export the namespace_sem semaphore so we can safely walk the
process's namespace to find a vfsmount with a root dentry matching
the dentry we are trying to mediate. We believe a cleaner solution
(such as passing a vfsmount or nameidata to all LSM hooks throughout
the VFS layer) would be useful for audit, other LSMs, and
potentially FUSE. As it is a fair amount of work to pass vfsmount or
nameidata structures throughout the VFS, alternative suggestions
and ideas are welcomed.

Thanks and Acknowledgment:
----------------------------------

1. AppArmor started life as Steve Beattie's thesis topic in 1996 and
has been in continuous development since.
2. Professors Virgil Gligor and Heather Hinton contributed
substantially to the initial design of AppArmor.
3. LSM was built with cooperation from a great many people; the LSM
interface reduced our long-term maintenance costs and helped raise
the visibility of mandatory access control systems among many
users. We wish to thank Stephen Smalley, James Morris, Chris
Wright, and Greg Kroah-Hartman in particular for their work on LSM.
4. The users of Immunix Linux and AppArmor on various Linuxes helped
a lot to improve the system.
5. The SUSE Security Team and the SUSE Kernel Team reviewed the
AppArmor code to help make it more ready for LKML inclusion. Of
course as usual, bugs are our own.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:49:13 UTC
Permalink
This patch glues AppArmor into the security configuration and Makefile.
It also creates the AppArmor configuration and Makefile.


Signed-off-by: Tony Jones <***@suse.de>

---
MAINTAINERS | 7 +++++++
security/Kconfig | 1 +
security/Makefile | 1 +
security/apparmor/Kconfig | 9 +++++++++
security/apparmor/Makefile | 6 ++++++
5 files changed, 24 insertions(+)

--- linux-2.6.17-rc1.orig/MAINTAINERS
+++ linux-2.6.17-rc1/MAINTAINERS
@@ -284,6 +284,13 @@
W: http://www.canb.auug.org.au/~sfr/
S: Supported

+APPARMOR SECURITY MODULE
+P: Tony Jones
+M: ***@suse.de
+L: apparmor-***@forge.novell.com
+W: http://forge.novell.com/modules/xfmod/project/?apparmor
+S: Supported
+
APPLETALK NETWORK LAYER
P: Arnaldo Carvalho de Melo
M: ***@conectiva.com.br
--- linux-2.6.17-rc1.orig/security/Kconfig
+++ linux-2.6.17-rc1/security/Kconfig
@@ -100,6 +100,7 @@
If you are unsure how to answer this question, answer N.

source security/selinux/Kconfig
+source security/apparmor/Kconfig

endmenu

--- linux-2.6.17-rc1.orig/security/Makefile
+++ linux-2.6.17-rc1/security/Makefile
@@ -4,6 +4,7 @@

obj-$(CONFIG_KEYS) += keys/
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
+subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor

# if we don't select a security model, use the default capabilities
ifneq ($(CONFIG_SECURITY),y)
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_APPARMOR
+ tristate "AppArmor support"
+ depends on SECURITY!=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.
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/Makefile
@@ -0,0 +1,6 @@
+# Makefile for AppArmor Linux Security Module
+#
+subdir-$(CONFIG_SECURITY_APPARMOR) += match
+obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
+
+apparmor-y := main.o list.o procattr.o lsm.o apparmorfs.o capabilities.o module_interface.o
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Arjan van de Ven
2006-04-19 17:57:19 UTC
Permalink
Post by Tony Jones
This patch glues AppArmor into the security configuration and Makefile.
It also creates the AppArmor configuration and Makefile.
please use a "proper" patch ordering; it's not possible to apply this
patch only and get a building kernel, breaking bisection

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 18:10:45 UTC
Permalink
Post by Arjan van de Ven
Post by Tony Jones
This patch glues AppArmor into the security configuration and Makefile.
It also creates the AppArmor configuration and Makefile.
please use a "proper" patch ordering; it's not possible to apply this
patch only and get a building kernel, breaking bisection
Fair enough, sorry. If we repost I will make sure I do this.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
V***@vt.edu
2006-04-19 18:35:41 UTC
Permalink
Post by Tony Jones
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_APPARMOR
+ tristate "AppArmor support"
+ depends on SECURITY!=n
+ help
This probably needs to be

depends on SECURITY!=n && SELINUX!=y

unless you want to get into some *really* messy composition issues....
Adrian Bunk
2006-04-19 19:55:02 UTC
Permalink
Post by Tony Jones
...
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_APPARMOR
+ tristate "AppArmor support"
+ depends on SECURITY!=n
...
Are you _really_ sure SECURITY=m, SECURITY_APPARMOR=y is a valid
configuration?

cu
Adrian
--
"Is there not promise of rain?" Ling Tan asked suddenly out
of the darkness. There had been need of rain for many days.
"Only a promise," Lao Er said.
Pearl S. Buck - Dragon Seed

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 20:52:18 UTC
Permalink
Post by Adrian Bunk
Post by Tony Jones
...
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_APPARMOR
+ tristate "AppArmor support"
+ depends on SECURITY!=n
...
Are you _really_ sure SECURITY=m, SECURITY_APPARMOR=y is a valid
configuration?
Thanks Adrian. Others made the exact same point. My bad. Fixed already.

Thanks
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:49:37 UTC
Permalink
This patch implements core AppArmor access control (where appropriate using
functionality provided by the sub matching module [apparmor_match]), code to
assign policy (subdomains and by connection profiles) to tasks during task
creation, remove them during task release and for determining appropiate
confinement upon domain changes (exec). It is also responsible for the low
level implementation of change_hat, switching confined tasks between their
primary and child profiles. Finally it implements the interface to the kernel
audit subsystem through which enforcement and learning events are passed to
userspace.


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/main.c | 1618 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1618 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/main.c
@@ -0,0 +1,1618 @@
+/*
+ * Copyright (C) 2002-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor Core
+ */
+
+#include <linux/security.h>
+#include <linux/namei.h>
+#include <linux/audit.h>
+
+#include "apparmor.h"
+#include "match/match.h"
+
+#include "inline.h"
+
+/* NULL complain profile
+ *
+ * Used when in complain mode, to emit Permitting messages for non-existant
+ * profiles and hats. This is necessary because of selective mode, in which
+ * case we need a complain null_profile and enforce null_profile
+ *
+ * The null_complain_profile cannot be statically allocated, because it
+ * can be associated to files which keep their reference even if subdomain is
+ * unloaded
+ */
+struct aaprofile *null_complain_profile;
+
+/***************************
+ * Private utility functions
+ **************************/
+
+/**
+ * aa_taskattr_access
+ * @procrelname: name of file to check permission
+ *
+ * Determine if request is for write access to /proc/self/attr/current
+ * This file is the usermode iterface for changing it's hat.
+ */
+static inline int aa_taskattr_access(const char *procrelname)
+{
+ char buf[sizeof("/attr/current") + 10];
+ const int maxbuflen = sizeof(buf);
+ /* assumption, 32bit pid (10 decimal digits incl \0) */
+
+ snprintf(buf, maxbuflen, "%d/attr/current", current->pid);
+ buf[maxbuflen - 1] = 0;
+
+ return strcmp(buf, procrelname) == 0;
+}
+
+/**
+ * aa_file_mode - get full mode for file entry from profile
+ * @profile: profile
+ * @name: filename
+ */
+static inline int aa_file_mode(struct aaprofile *profile, const char *name)
+{
+ struct aa_entry *entry;
+ int mode = 0;
+
+ AA_DEBUG("%s: %s\n", __FUNCTION__, name);
+ if (!name) {
+ AA_DEBUG("%s: no name\n", __FUNCTION__);
+ goto out;
+ }
+
+ if (!profile) {
+ AA_DEBUG("%s: no profile\n", __FUNCTION__);
+ goto out;
+ }
+ list_for_each_entry(entry, &profile->file_entry, list) {
+ if (aamatch_match(name, entry->filename,
+ entry->type, entry->extradata))
+ mode |= entry->mode;
+ }
+out:
+ return mode;
+}
+
+/**
+ * aa_get_execmode - calculate what qualifier to apply to an exec
+ * @active: profile to search
+ * @name: name of file to exec
+ * @xmod: pointer to a execution mode bit for the rule that was matched
+ * if the rule has no execuition qualifier {pui} then
+ * %AA_MAY_EXEC is returned indicating a naked x
+ * if the has an exec qualifier then only the qualifier bit {pui}
+ * is returned (%AA_MAY_EXEC) is not set.
+ *
+ * Returns %0 (false):
+ * if unable to find profile or there are conflicting pattern matches.
+ * *xmod - is not modified
+ *
+ * Returns %1 (true):
+ * if not confined
+ * *xmod = %AA_MAY_EXEC
+ * if exec rule matched
+ * if the rule has an execution mode qualifier {pui} then
+ * *xmod = the execution qualifier of the rule {pui}
+ * else
+ * *xmod = %AA_MAY_EXEC
+ */
+static inline int aa_get_execmode(struct aaprofile *active, const char *name,
+ int *xmod)
+{
+ struct aa_entry *entry;
+ struct aa_entry *match = NULL;
+
+ int pattern_match_invalid = 0, rc = 0;
+
+ /* search list of profiles with 'x' permission
+ * this will also include entries with 'p', 'u' and 'i'
+ * qualifiers.
+ *
+ * If we find a pattern match we will keep looking for an exact match
+ * If we find conflicting pattern matches we will flag (while still
+ * looking for an exact match). If all we have is a conflict, FALSE
+ * is returned.
+ */
+
+ list_for_each_entry(entry, &active->file_entryp[POS_AA_MAY_EXEC],
+ listp[POS_AA_MAY_EXEC]) {
+ if (!pattern_match_invalid &&
+ entry->type == aa_entry_pattern &&
+ aamatch_match(name, entry->filename,
+ entry->type, entry->extradata)) {
+ if (match &&
+ AA_EXEC_MASK(entry->mode) !=
+ AA_EXEC_MASK(match->mode))
+ pattern_match_invalid = 1;
+ else
+ /* keep searching for an exact match */
+ match = entry;
+ } else if ((entry->type == aa_entry_literal ||
+ (!pattern_match_invalid &&
+ entry->type == aa_entry_tailglob)) &&
+ aamatch_match(name, entry->filename,
+ entry->type,
+ entry->extradata)) {
+ if (entry->type == aa_entry_literal) {
+ /* got an exact match -- there can be only
+ * one, asserted at profile load time
+ */
+ match = entry;
+ pattern_match_invalid = 0;
+ break;
+ } else {
+ if (match &&
+ AA_EXEC_MASK(entry->mode) !=
+ AA_EXEC_MASK(match->mode))
+ pattern_match_invalid = 1;
+ else
+ /* got a tailglob match, keep searching
+ * for an exact match
+ */
+ match = entry;
+ }
+ }
+
+ }
+
+ rc = match && !pattern_match_invalid;
+
+ if (rc) {
+ int mode = AA_EXEC_MASK(match->mode);
+
+ /* check for qualifiers, if present
+ * we just return the qualifier
+ */
+ if (mode & ~AA_MAY_EXEC)
+ mode = mode & ~AA_MAY_EXEC;
+
+ *xmod = mode;
+ } else if (!match) {
+ AA_DEBUG("%s: Unable to find execute entry in profile "
+ "for image '%s'\n",
+ __FUNCTION__,
+ name);
+ } else if (pattern_match_invalid) {
+ AA_WARN("%s: Inconsistency in profile %s. "
+ "Two (or more) patterns specify conflicting exec "
+ "qualifiers ('u', 'i' or 'p') for image %s\n",
+ __FUNCTION__,
+ active->name,
+ name);
+ }
+
+ return rc;
+
+ *xmod = AA_MAY_EXEC;
+ return 1;
+}
+
+/**
+ * aa_filter_mask
+ * @mask: requested mask
+ * @inode: potential directory inode
+ *
+ * This fn performs pre-verification of the requested mask
+ * We ignore append. Previously we required 'w' on a dir to add a file.
+ * No longer. Now we require 'w' on just the file itself. Traversal 'x' is
+ * also ignored for directories.
+ *
+ * Returned value of %0 indicates no need to perform a perm check.
+ */
+static inline int aa_filter_mask(int mask, struct inode *inode)
+{
+ if (mask) {
+ int elim = MAY_APPEND;
+
+ if (inode && S_ISDIR(inode->i_mode))
+ elim |= (MAY_EXEC | MAY_WRITE);
+
+ mask &= ~elim;
+ }
+
+ return mask;
+}
+
+static inline void aa_permerror2result(int perm_result, struct aa_audit *sa)
+{
+ if (perm_result == 0) { /* success */
+ sa->result = 1;
+ sa->error_code = 0;
+ } else { /* -ve internal error code or +ve mask of denied perms */
+ sa->result = 0;
+ sa->error_code = perm_result;
+ }
+}
+
+/*************************
+ * Main internal functions
+ ************************/
+
+/**
+ * aa_file_perm - calculate access mode for file
+ * @active: profile to check against
+ * @name: name of file to calculate mode for
+ * @mask: permission mask requested for file
+ *
+ * Search the aa_entry list in @active.
+ * Search looking to verify all permissions passed in mask.
+ * Perform the search by looking at the partitioned list of entries, one
+ * partition per permission bit.
+ *
+ * Return %0 on success, else mask of non-allowed permissions
+ */
+static unsigned int aa_file_perm(struct aaprofile *active, const char *name,
+ int mask)
+{
+ int i, error = 0, mode;
+
+#define PROCPFX "/proc/"
+#define PROCLEN sizeof(PROCPFX) - 1
+
+ AA_DEBUG("%s: %s 0x%x\n", __FUNCTION__, name, mask);
+
+ /* should not enter with other than R/W/X/L */
+ WARN_ON(mask &
+ ~(AA_MAY_READ | AA_MAY_WRITE | AA_MAY_EXEC | AA_MAY_LINK));
+
+ /* Special case access to /proc/self/attr/current
+ * Currently we only allow access if opened O_WRONLY
+ */
+ if (mask == MAY_WRITE && strncmp(PROCPFX, name, PROCLEN) == 0 &&
+ (!list_empty(&BASE_PROFILE(active)->sub) ||
+ PROFILE_COMPLAIN(active)) && aa_taskattr_access(name + PROCLEN))
+ goto done;
+
+ mode = 0;
+
+ /* iterate over partition, one permission bit at a time */
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
+ struct aa_entry *entry;
+
+ /* do we have to accumulate this bit?
+ * or have we already accumulated it (shortcut below)? */
+ if (!(mask & (1 << i)) || mode & (1 << i))
+ continue;
+
+ list_for_each_entry(entry, &active->file_entryp[i],
+ listp[i]) {
+ if (aamatch_match(name, entry->filename,
+ entry->type, entry->extradata)) {
+ /* Shortcut, accumulate all bits present */
+ mode |= entry->mode;
+
+ /* Mask bits are overloaded
+ * MAY_{EXEC,WRITE,READ,APPEND} are used by
+ * kernel, other values are used locally only.
+ */
+ if ((mode & mask) == mask) {
+ AA_DEBUG("MATCH! %s=0x%x [total mode=0x%x]\n",
+ name, mask, mode);
+
+ goto done;
+ }
+ }
+ }
+ }
+
+ /* return permissions not satisfied */
+ error = mask & ~mode;
+
+done:
+ return error;
+}
+
+/**
+ * aa_link_perm - test permission to link to a file
+ * @active: profile to check against
+ * @link: name of link being created
+ * @target: name of target to be linked to
+ *
+ * Look up permission mode on both @link and @target. @link must have same
+ * permission mode as @target. At least @link must have the link bit enabled.
+ * Return %0 on success, error otherwise.
+ */
+static int aa_link_perm(struct aaprofile *active,
+ const char *link, const char *target)
+{
+ int l_mode, t_mode, ret;
+
+ l_mode = aa_file_mode(active, link);
+ if (l_mode & AA_MAY_LINK) {
+ /* mask off link bit */
+ l_mode &= ~AA_MAY_LINK;
+
+ t_mode = aa_file_mode(active, target);
+ t_mode &= ~AA_MAY_LINK;
+
+ ret = (l_mode == t_mode);
+ } else {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * _aa_perm_dentry
+ * @active: profile to check against
+ * @dentry: requested dentry
+ * @mask: mask of requested operations
+ * @pname: pointer to hold matched pathname (if any)
+ *
+ * Helper function. Obtain pathname for specified dentry. Verify if profile
+ * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly
+ * necessary to search mountpoints in namespace -- when nameidata is passed
+ * more fully, this code can go away). If more than one mountpoint matches
+ * but none satisfy the profile, only the first pathname (mountpoint) is
+ * returned for subsequent logging.
+ *
+ * Return %0 (success), +ve (mask of permissions not satisfied) or -ve (system
+ * error, most likely -%ENOMEM).
+ */
+static int _aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
+ int mask, const char **pname)
+{
+ char *name = NULL, *failed_name = NULL;
+ struct aa_path_data data;
+ int error = 0, failed_error = 0, path_error,
+ complain = PROFILE_COMPLAIN(active);
+
+ /* search all paths to dentry */
+
+ aa_path_begin(dentry, &data);
+ do {
+ name = aa_path_getname(&data);
+ if (name) {
+ /* error here is 0 (success) or +ve (mask of perms) */
+ error = aa_file_perm(active, name, mask);
+
+ /* access via any path is enough */
+ if (complain || error == 0)
+ break; /* Caller must free name */
+
+ /* Already have an path that failed? */
+ if (failed_name) {
+ aa_put_name(name);
+ } else {
+ failed_name = name;
+ failed_error = error;
+ }
+ }
+ } while (name);
+
+ if ((path_error = aa_path_end(&data)) != 0) {
+ AA_ERROR("%s: An error occured while translating dentry %p "
+ "inode# %lu to a pathname. Error %d\n",
+ __FUNCTION__,
+ dentry,
+ dentry->d_inode->i_ino,
+ path_error);
+
+ WARN_ON(name); /* name should not be set if error */
+ error = path_error;
+ name = NULL;
+ } else if (name) {
+ if (failed_name)
+ aa_put_name(failed_name);
+ } else {
+ name = failed_name;
+ error = failed_error;
+ }
+
+ *pname = name;
+
+ return error;
+}
+
+/**************************
+ * Global utility functions
+ *************************/
+
+/**
+ * attach_nullprofile - allocate and attach a null_profile hat to profile
+ * @profile: profile to attach a null_profile hat to.
+ *
+ * Return %0 (success) or error (-%ENOMEM)
+ */
+int attach_nullprofile(struct aaprofile *profile)
+{
+ struct aaprofile *hat = NULL;
+ char *hatname = NULL;
+
+ hat = alloc_aaprofile();
+ if (!hat)
+ goto fail;
+ if (profile->flags.complain)
+ hatname = kstrdup("null-complain-profile", GFP_KERNEL);
+ else
+ hatname = kstrdup("null-profile", GFP_KERNEL);
+ if (!hatname)
+ goto fail;
+
+ hat->flags.complain = profile->flags.complain;
+ hat->name = hatname;
+ hat->parent = profile;
+
+ profile->null_profile = hat;
+
+ return 0;
+
+fail:
+ kfree(hatname);
+ free_aaprofile(hat);
+
+ return -ENOMEM;
+}
+
+
+/**
+ * alloc_null_complain_profile - Allocate the global null_complain_profile.
+ *
+ * Return %0 (success) or error (-%ENOMEM)
+ */
+int alloc_null_complain_profile(void)
+{
+ null_complain_profile = alloc_aaprofile();
+ if (!null_complain_profile)
+ goto fail;
+
+ null_complain_profile->name =
+ kstrdup("null-complain-profile", GFP_KERNEL);
+
+ if (!null_complain_profile->name)
+ goto fail;
+
+ null_complain_profile->flags.complain = 1;
+ if (attach_nullprofile(null_complain_profile))
+ goto fail;
+
+ return 0;
+
+fail:
+ /* free_aaprofile is safe for freeing partially constructed objects */
+ free_aaprofile(null_complain_profile);
+ null_complain_profile = NULL;
+
+ return -ENOMEM;
+}
+
+/**
+ * free_null_complain_profile - Free null profiles
+ */
+void free_null_complain_profile(void)
+{
+ put_aaprofile(null_complain_profile);
+ null_complain_profile = NULL;
+}
+
+/**
+ * aa_audit_message - Log a message to the audit subsystem
+ * @active: profile to check against
+ * @gfp: allocation flags
+ * @flags: audit flags
+ * @fmt: varargs fmt
+ */
+int aa_audit_message(struct aaprofile *active, unsigned int gfp, int flags,
+ const char *fmt, ...)
+{
+ int ret;
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_MSG;
+ sa.name = fmt;
+ va_start(sa.vaval, fmt);
+ sa.flags = flags;
+ sa.gfp_mask = gfp;
+ sa.error_code = 0;
+ sa.result = 0; /* fake failure: force message to be logged */
+
+ ret = aa_audit(active, &sa);
+
+ va_end(sa.vaval);
+
+ return ret;
+}
+
+/**
+ * aa_audit_syscallreject - Log a syscall rejection to the audit subsystem
+ * @active: profile to check against
+ * @msg: string describing syscall being rejected
+ * @gfp: memory allocation flags
+ */
+int aa_audit_syscallreject(struct aaprofile *active, unsigned int gfp,
+ const char *msg)
+{
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_SYSCALL;
+ sa.name = msg;
+ sa.flags = 0;
+ sa.gfp_mask = gfp;
+ sa.error_code = 0;
+ sa.result = 0; /* failure */
+
+ return aa_audit(active, &sa);
+}
+
+/**
+ * aa_audit - Log an audit event to the audit subsystem
+ * @active: profile to check against
+ * @sa: audit event
+ */
+int aa_audit(struct aaprofile *active, const struct aa_audit *sa)
+{
+ struct audit_buffer *ab = NULL;
+ struct audit_context *ctx;
+
+ const char *logcls;
+ unsigned int flags;
+ int audit = 0,
+ complain = 0,
+ error = -EINVAL,
+ opspec_error = -EACCES;
+
+ const unsigned int gfp_mask = sa->gfp_mask;
+
+ WARN_ON(sa->type >= AA_AUDITTYPE__END);
+
+ /*
+ * sa->result: 1 success, 0 failure
+ * sa->error_code: success: 0
+ * failure: +ve mask of failed permissions or -ve
+ * system error
+ */
+
+ if (likely(sa->result)) {
+ if (likely(!PROFILE_AUDIT(active))) {
+ /* nothing to log */
+ error = 0;
+ goto out;
+ } else {
+ audit = 1;
+ logcls = "AUDITING";
+ }
+ } else if (sa->error_code < 0) {
+ audit_log(current->audit_context, gfp_mask, AUDIT_AA,
+ "Internal error auditing event type %d (error %d)",
+ sa->type, sa->error_code);
+ AA_ERROR("Internal error auditing event type %d (error %d)\n",
+ sa->type, sa->error_code);
+ error = sa->error_code;
+ goto out;
+ } else if (sa->type == AA_AUDITTYPE_SYSCALL) {
+ /* Currently AA_AUDITTYPE_SYSCALL is for rejects only.
+ * Values set by aa_audit_syscallreject will get us here.
+ */
+ logcls = "REJECTING";
+ } else {
+ complain = PROFILE_COMPLAIN(active);
+ logcls = complain ? "PERMITTING" : "REJECTING";
+ }
+
+ /* In future extend w/ per-profile flags
+ * (flags |= sa->active->flags)
+ */
+ flags = sa->flags;
+ if (apparmor_logsyscall)
+ flags |= AA_AUDITFLAG_AUDITSS_SYSCALL;
+
+
+ /* Force full audit syscall logging regardless of global setting if
+ * we are rejecting a syscall
+ */
+ if (sa->type == AA_AUDITTYPE_SYSCALL) {
+ ctx = current->audit_context;
+ } else {
+ ctx = (flags & AA_AUDITFLAG_AUDITSS_SYSCALL) ?
+ current->audit_context : NULL;
+ }
+
+ ab = audit_log_start(ctx, gfp_mask, AUDIT_AA);
+
+ if (!ab) {
+ AA_ERROR("Unable to log event (%d) to audit subsys\n",
+ sa->type);
+ if (complain)
+ error = 0;
+ goto out;
+ }
+
+ /* messages get special handling */
+ if (sa->type == AA_AUDITTYPE_MSG) {
+ audit_log_vformat(ab, sa->name, sa->vaval);
+ audit_log_end(ab);
+ error = 0;
+ goto out;
+ }
+
+ /* log operation */
+
+ audit_log_format(ab, "%s ", logcls); /* REJECTING/ALLOWING/etc */
+
+ if (sa->type == AA_AUDITTYPE_FILE) {
+ int perm = audit ? sa->ival : sa->error_code;
+
+ audit_log_format(ab, "%s%s%s%s access to %s ",
+ perm & AA_MAY_READ ? "r" : "",
+ perm & AA_MAY_WRITE ? "w" : "",
+ perm & AA_MAY_EXEC ? "x" : "",
+ perm & AA_MAY_LINK ? "l" : "",
+ sa->name);
+
+ opspec_error = -EPERM;
+
+ } else if (sa->type == AA_AUDITTYPE_DIR) {
+ audit_log_format(ab, "%s on %s ",
+ sa->ival == aa_dir_mkdir ? "mkdir" : "rmdir",
+ sa->name);
+
+ } else if (sa->type == AA_AUDITTYPE_ATTR) {
+ struct iattr *iattr = (struct iattr*)sa->pval;
+
+ audit_log_format(ab,
+ "attribute (%s%s%s%s%s%s%s) change to %s ",
+ iattr->ia_valid & ATTR_MODE ? "mode," : "",
+ iattr->ia_valid & ATTR_UID ? "uid," : "",
+ iattr->ia_valid & ATTR_GID ? "gid," : "",
+ iattr->ia_valid & ATTR_SIZE ? "size," : "",
+ ((iattr->ia_valid & ATTR_ATIME_SET) ||
+ (iattr->ia_valid & ATTR_ATIME)) ? "atime," : "",
+ ((iattr->ia_valid & ATTR_MTIME_SET) ||
+ (iattr->ia_valid & ATTR_MTIME)) ? "mtime," : "",
+ iattr->ia_valid & ATTR_CTIME ? "ctime," : "",
+ sa->name);
+
+ } else if (sa->type == AA_AUDITTYPE_XATTR) {
+ const char *fmt;
+ switch (sa->ival) {
+ case aa_xattr_get:
+ fmt = "xattr get";
+ break;
+ case aa_xattr_set:
+ fmt = "xattr set";
+ break;
+ case aa_xattr_list:
+ fmt = "xattr list";
+ break;
+ case aa_xattr_remove:
+ fmt = "xattr remove";
+ break;
+ default:
+ fmt = "xattr <unknown>";
+ break;
+ }
+
+ audit_log_format(ab, "%s on %s ", fmt, sa->name);
+
+ } else if (sa->type == AA_AUDITTYPE_LINK) {
+ audit_log_format(ab,
+ "link access from %s to %s ",
+ sa->name,
+ (char*)sa->pval);
+
+ } else if (sa->type == AA_AUDITTYPE_CAP) {
+ audit_log_format(ab,
+ "access to capability '%s' ",
+ capability_to_name(sa->ival));
+
+ opspec_error = -EPERM;
+ } else if (sa->type == AA_AUDITTYPE_SYSCALL) {
+ audit_log_format(ab, "access to syscall '%s' ", sa->name);
+
+ opspec_error = -EPERM;
+ } else {
+ /* -EINVAL -- will WARN_ON above */
+ goto out;
+ }
+
+ audit_log_format(ab, "(%s(%d) ", current->comm, current->pid);
+
+ if (0)
+ audit_log_format(ab, "[global deny])");
+ else
+ audit_log_format(ab, "profile %s active %s)",
+ BASE_PROFILE(active)->name,
+ active->name);
+
+ audit_log_end(ab);
+
+ if (complain)
+ error = 0;
+ else
+ error = sa->result ? 0 : opspec_error;
+
+out:
+ return error;
+}
+
+/**
+ * aa_get_name - retrieve fully qualified path name
+ * @dentry: relative path element
+ * @mnt: where in tree
+ *
+ * Returns fully qualified path name on sucess, NULL on failure.
+ * aa_put_name must be used to free allocated buffer.
+ */
+char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt)
+{
+ char *page, *name = NULL;
+
+ page = (char *)__get_free_page(GFP_KERNEL);
+ if (!page)
+ goto out;
+
+ name = d_path_flags(dentry, mnt, page, PAGE_SIZE,
+ DPATH_SYSROOT|DPATH_NODELETED);
+
+ AA_DEBUG("%s: full_path=%s\n", __FUNCTION__, name);
+out:
+ return name;
+}
+
+/***********************************
+ * Global permission check functions
+ ***********************************/
+
+/**
+ * aa_attr - check whether attribute change allowed
+ * @active: profile to check against
+ * @dentry: file to check
+ * @iattr: attribute changes requested
+ */
+int aa_attr(struct aaprofile *active, struct dentry *dentry,
+ struct iattr *iattr)
+{
+ int error = 0, permerror;
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_ATTR;
+ sa.pval = iattr;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ permerror = _aa_perm_dentry(active, dentry, MAY_WRITE, &sa.name);
+ aa_permerror2result(permerror, &sa);
+
+ error = aa_audit(active, &sa);
+
+ aa_put_name(sa.name);
+
+ return error;
+}
+
+/**
+ * aa_xattr - check whether xattr attribute change allowed
+ * @active: profile to check against
+ * @dentry: file to check
+ * @xattr: xattr to check
+ * @xattroptype: type of xattr operation
+ */
+int aa_xattr(struct aaprofile *active, struct dentry *dentry,
+ const char *xattr, enum aa_xattroptype xattroptype)
+{
+ int error = 0, permerror, mask = 0;
+ struct aa_audit sa;
+
+ /* if not confined or empty mask permission granted */
+ if (!active)
+ goto out;
+
+ if (xattroptype == aa_xattr_get || xattroptype == aa_xattr_list)
+ mask = MAY_READ;
+ else if (xattroptype == aa_xattr_set || xattroptype == aa_xattr_remove)
+ mask = MAY_WRITE;
+
+ sa.type = AA_AUDITTYPE_XATTR;
+ sa.ival = xattroptype;
+ sa.pval = xattr;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ permerror = _aa_perm_dentry(active, dentry, mask, &sa.name);
+ aa_permerror2result(permerror, &sa);
+
+ error = aa_audit(active, &sa);
+
+ aa_put_name(sa.name);
+
+out:
+ return error;
+}
+
+/**
+ * aa_perm - basic subdomain permissions check
+ * @active: profile to check against
+ * @dentry: dentry
+ * @mnt: mountpoint
+ * @mask: access mode requested
+ *
+ * Determine if access (mask) for dentry is authorized by subdomain active
+ * profile. Result, %0 (success), -ve (error)
+ */
+int aa_perm(struct aaprofile *active, struct dentry *dentry,
+ struct vfsmount *mnt, int mask)
+{
+ int error = 0, permerror;
+ struct aa_audit sa;
+
+ if (!active)
+ goto out;
+
+ if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0)
+ goto out;
+
+ sa.type = AA_AUDITTYPE_FILE;
+ sa.name = aa_get_name(dentry, mnt);
+ sa.ival = mask;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ permerror = (sa.name ? aa_file_perm(active, sa.name, mask) : -ENOMEM);
+
+ aa_permerror2result(permerror, &sa);
+
+ error = aa_audit(active, &sa);
+
+ aa_put_name(sa.name);
+
+out:
+ return error;
+}
+
+/**
+ * aa_perm_nameidata: interface to sd_perm accepting nameidata
+ * @active: profile to check against
+ * @nd: namespace data (for vfsmnt and dentry)
+ * @mask: access mode requested
+ */
+int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd, int mask)
+{
+ int error = 0;
+
+ if (nd)
+ error = aa_perm(active, nd->dentry, nd->mnt, mask);
+
+ return error;
+}
+
+/**
+ * aa_perm_dentry - file permissions interface when no vfsmnt available
+ * @active: profile to check against
+ * @dentry: requested dentry
+ * @mask: access mode requested
+ *
+ * Determine if access (mask) for dentry is authorized by active profile.
+ * Result, %0 (success), -ve (error)
+ */
+int aa_perm_dentry(struct aaprofile *active, struct dentry *dentry, int mask)
+{
+ int error = 0, permerror;
+ struct aa_audit sa;
+
+ if (!active)
+ goto out;
+
+ if ((mask = aa_filter_mask(mask, dentry->d_inode)) == 0)
+ goto out;
+
+ sa.type = AA_AUDITTYPE_FILE;
+ sa.ival = mask;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ permerror = _aa_perm_dentry(active, dentry, mask, &sa.name);
+ aa_permerror2result(permerror, &sa);
+
+ error = aa_audit(active, &sa);
+
+ aa_put_name(sa.name);
+
+out:
+ return error;
+}
+
+/**
+ * aa_perm_dir
+ * @active: profile to check against
+ * @dentry: requested dentry
+ * @diroptype: aa_dir_mkdir or aa_dir_rmdir
+ *
+ * Determine if directory operation (make/remove) for dentry is authorized
+ * by @active profile.
+ * Result, %0 (success), -ve (error)
+ */
+int aa_perm_dir(struct aaprofile *active, struct dentry *dentry,
+ enum aa_diroptype diroptype)
+{
+ int error = 0, permerror, mask;
+ struct aa_audit sa;
+
+ WARN_ON(diroptype != aa_dir_mkdir && diroptype != aa_dir_rmdir);
+
+ if (!active)
+ goto out;
+
+ mask = MAY_WRITE;
+
+ sa.type = AA_AUDITTYPE_DIR;
+ sa.ival = diroptype;
+ sa.flags = 0;
+ sa.gfp_mask = GFP_KERNEL;
+
+ permerror = _aa_perm_dentry(active, dentry, mask, &sa.name);
+ aa_permerror2result(permerror, &sa);
+
+ error = aa_audit(active, &sa);
+
+ aa_put_name(sa.name);
+
+out:
+ return error;
+}
+
+/**
+ * aa_capability - test permission to use capability
+ * @active: profile to check against
+ * @cap: capability to be tested
+ *
+ * Look up capability in active profile capability set.
+ * Return %0 (success), -%EPERM (error)
+ */
+int aa_capability(struct aaprofile *active, int cap)
+{
+ int error = 0;
+
+ struct aa_audit sa;
+
+ sa.type = AA_AUDITTYPE_CAP;
+ sa.name = NULL;
+ sa.ival = cap;
+ sa.flags = 0;
+ sa.error_code = 0;
+ sa.result = cap_raised(active->capabilities, cap);
+ sa.gfp_mask = GFP_ATOMIC;
+
+ error = aa_audit(active, &sa);
+
+ return error;
+}
+
+/**
+ * aa_link - hard link check
+ * @active: profile to check against
+ * @link: dentry for link being created
+ * @target: dentry for link target
+ *
+ * Checks link permissions for all possible name combinations. This is
+ * particularly ugly. Returns %0 on sucess, error otherwise.
+ */
+int aa_link(struct aaprofile *active, struct dentry *link,
+ struct dentry *target)
+{
+ char *iname = NULL, *oname = NULL,
+ *failed_iname = NULL, *failed_oname = NULL;
+ unsigned int result = 0;
+ int error, path_error, error_code = 0, match = 0,
+ complain = PROFILE_COMPLAIN(active);
+ struct aa_path_data idata, odata;
+ struct aa_audit sa;
+
+ if (!active)
+ return 0;
+
+ /* Perform nested lookup for names.
+ * This is necessary in the case where /dev/block is mounted
+ * multiple times, i.e /dev/block->/a and /dev/block->/b
+ * This allows us to detect links where src/dest are on different
+ * mounts. N.B no support yet for links across bind mounts of
+ * the form mount -bind /mnt/subpath /mnt2
+ *
+ * Getting direct access to vfsmounts (via nameidata) for link and
+ * target would allow all this uglyness to go away.
+ *
+ * If more than one mountpoint matches but none satisfy the profile,
+ * only the first pathname (mountpoint) is logged.
+ */
+
+ __aa_path_begin(target, link, &odata);
+ do {
+ oname = aa_path_getname(&odata);
+ if (oname) {
+ aa_path_begin(target, &idata);
+ do {
+ iname = aa_path_getname(&idata);
+ if (iname) {
+ result = aa_link_perm(active, oname,
+ iname);
+
+ /* access via any path is enough */
+ if (result || complain) {
+ match = 1;
+ break;
+ }
+
+ /* Already have an path that failed? */
+ if (failed_iname) {
+ aa_put_name(iname);
+ } else {
+ failed_iname = iname;
+ failed_oname = oname;
+ }
+ }
+ } while (iname && !match);
+
+ /* should not be possible if we matched */
+ if ((path_error = aa_path_end(&idata)) != 0) {
+ AA_ERROR("%s: An error occured while "
+ "translating inner dentry %p "
+ "inode %lu to a pathname. Error %d\n",
+ __FUNCTION__,
+ target,
+ target->d_inode->i_ino,
+ path_error);
+
+ /* name should not be set if error */
+ WARN_ON(iname);
+
+ error_code = path_error;
+ }
+
+ /* don't release if we're saving it */
+ if (!match && failed_oname != oname)
+ aa_put_name(oname);
+ }
+ } while (oname && !match);
+
+ if (error_code != 0) {
+ /* inner error */
+ (void)aa_path_end(&odata);
+ } else if ((path_error = aa_path_end(&odata)) != 0) {
+ AA_ERROR("%s: An error occured while translating outer "
+ "dentry %p inode %lu to a pathname. Error %d\n",
+ __FUNCTION__,
+ link,
+ link->d_inode->i_ino,
+ path_error);
+
+ error_code = path_error;
+ }
+
+ if (error_code != 0) {
+ /* inner or outer error */
+ result = 0;
+ } else if (match) {
+ result = 1;
+ } else {
+ /* failed to match */
+ WARN_ON(iname);
+ WARN_ON(oname);
+
+ result = 0;
+ iname = failed_iname;
+ oname = failed_oname;
+ }
+
+ sa.type = AA_AUDITTYPE_LINK;
+ sa.name = oname; /* link */
+ sa.pval = iname; /* target */
+ sa.flags = 0;
+ sa.error_code = error_code;
+ sa.result = result;
+ sa.gfp_mask = GFP_KERNEL;
+
+ error = aa_audit(active, &sa);
+
+ if (failed_oname != oname)
+ aa_put_name(failed_oname);
+ if (failed_iname != iname)
+ aa_put_name(failed_iname);
+
+ aa_put_name(oname);
+ aa_put_name(iname);
+
+ return error;
+}
+
+/*******************************
+ * Global task related functions
+ *******************************/
+
+/**
+ * aa_fork - create a new subdomain
+ * @p: new process
+ *
+ * Create a new subdomain struct for the newly created process @p.
+ * Copy parent info to child. If parent has no subdomain, child
+ * will get one with %NULL values. Return %0 on sucess.
+ *
+ * The sd_lock is used to maintain consistency against profile
+ * replacement/removal.
+ */
+
+int aa_fork(struct task_struct *p)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ struct subdomain *newsd = alloc_subdomain(p);
+
+ AA_DEBUG("%s\n", __FUNCTION__);
+
+ if (!newsd)
+ return -ENOMEM;
+
+ if (sd) {
+ unsigned long flags;
+
+ /* Use locking here instead of getting the reference
+ * because we need both the old reference and the
+ * new reference to be consistent.
+ */
+ spin_lock_irqsave(&sd_lock, flags);
+ aa_switch(newsd, sd->active);
+ newsd->hat_magic = sd->hat_magic;
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ if (SUBDOMAIN_COMPLAIN(sd) &&
+ sd->active == null_complain_profile)
+ LOG_HINT(sd->active, GFP_KERNEL, HINT_FORK,
+ "pid=%d child=%d\n",
+ current->pid, p->pid);
+ }
+ p->security = newsd;
+ return 0;
+}
+
+/**
+ * aa_register - register a new program
+ * @filp: file of program being registered
+ *
+ * Try to register a new program during execve(). This should give the
+ * new program a valid subdomain.
+ */
+int aa_register(struct file *filp)
+{
+ char *filename;
+ struct subdomain *sd;
+ struct aaprofile *active;
+ struct aaprofile *newprofile = NULL, unconstrained_flag;
+ int error = -ENOMEM,
+ exec_mode = 0,
+ find_profile = 0,
+ find_profile_mandatory = 0,
+ complain = 0;
+
+ AA_DEBUG("%s\n", __FUNCTION__);
+
+ sd = AA_SUBDOMAIN(current->security);
+
+ if (sd) {
+ complain = SUBDOMAIN_COMPLAIN(sd);
+ } else {
+ /* task has no subdomain. This can happen when a task is
+ * created when subdomain is not loaded. Allocate and
+ * attach a subdomain to the task
+ */
+ sd = alloc_subdomain(current);
+ if (!sd) {
+ AA_WARN("%s: Failed to allocate subdomain\n",
+ __FUNCTION__);
+ goto out;
+ }
+
+ current->security = sd;
+ }
+
+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt);
+ if (!filename) {
+ AA_WARN("%s: Failed to get filename\n", __FUNCTION__);
+ goto out;
+ }
+
+ error = 0;
+
+ active = get_active_aaprofile();
+
+ if (!active) {
+ /* Unconfined task, load profile if it exists */
+ find_profile = 1;
+ goto find_profile;
+ }
+
+ /* Confined task, determine what mode inherit, unconstrained or
+ * mandatory to load new profile
+ */
+ if (aa_get_execmode(active, filename, &exec_mode)) {
+ switch (exec_mode) {
+ case AA_EXEC_INHERIT:
+ /* do nothing - setting of profile
+ * already handed in aa_fork
+ */
+ AA_DEBUG("%s: INHERIT %s\n",
+ __FUNCTION__,
+ filename);
+ break;
+
+ case AA_EXEC_UNCONSTRAINED:
+ AA_DEBUG("%s: UNCONSTRAINED %s\n",
+ __FUNCTION__,
+ filename);
+
+ /* unload profile */
+ newprofile = &unconstrained_flag;
+ break;
+
+ case AA_EXEC_PROFILE:
+ AA_DEBUG("%s: PROFILE %s\n",
+ __FUNCTION__,
+ filename);
+
+ find_profile = 1;
+ find_profile_mandatory = 1;
+ break;
+
+ case AA_MAY_EXEC:
+ /* this should not happen, entries
+ * with just EXEC only should be
+ * rejected at profile load time
+ */
+ AA_ERROR("%s: Rejecting exec(2) of image '%s'. "
+ "AA_MAY_EXEC without exec qualifier invalid "
+ "(%s(%d) profile %s active %s\n",
+ __FUNCTION__,
+ filename,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -EPERM;
+ break;
+
+ default:
+ AA_ERROR("%s: Rejecting exec(2) of image '%s'. "
+ "Unknown exec qualifier %x "
+ "(%s (pid %d) profile %s active %s)\n",
+ __FUNCTION__,
+ filename,
+ exec_mode,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, sd->active->name);
+ error = -EPERM;
+ break;
+ }
+
+ } else if (complain) {
+ /* There was no entry in calling profile
+ * describing mode to execute image in.
+ * Drop into null-profile
+ */
+ newprofile = get_aaprofile(null_complain_profile);
+ } else {
+ AA_WARN("%s: Rejecting exec(2) of image '%s'. "
+ "Unable to determine exec qualifier "
+ "(%s (pid %d) profile %s active %s)\n",
+ __FUNCTION__,
+ filename,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -EPERM;
+ }
+
+
+find_profile:
+ if (!find_profile)
+ goto apply_profile;
+
+ /* Locate new profile */
+ newprofile = aa_profilelist_find(filename);
+ if (newprofile) {
+ AA_DEBUG("%s: setting profile %s\n",
+ __FUNCTION__, newprofile->name);
+ } else if (find_profile_mandatory) {
+ /* Profile (mandatory) could not be found */
+
+ if (complain) {
+ LOG_HINT(active, GFP_KERNEL, HINT_MANDPROF,
+ "image=%s pid=%d profile=%s active=%s\n",
+ filename,
+ current->pid,
+ BASE_PROFILE(active)->name, active->name);
+
+ newprofile = get_aaprofile(null_complain_profile);
+ } else {
+ AA_WARN("REJECTING exec(2) of image '%s'. "
+ "Profile mandatory and not found "
+ "(%s(%d) profile %s active %s)\n",
+ filename,
+ current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+ error = -EPERM;
+ }
+ } else {
+ /* Profile (non-mandatory) could not be found */
+
+ /* Only way we can get into this code is if task
+ * is unconstrained.
+ */
+
+ WARN_ON(active);
+
+ AA_DEBUG("%s: No profile found for exec image %s\n",
+ __FUNCTION__,
+ filename);
+ } /* newprofile */
+
+
+apply_profile:
+ /* Apply profile if necessary */
+ if (newprofile) {
+ unsigned long flags;
+
+ if (newprofile == &unconstrained_flag)
+ newprofile = NULL;
+
+ /* grab a lock - this is to guarentee consistency against
+ * other writers of subdomain (replacement/removal)
+ *
+ * Several things may have changed since the code above
+ *
+ * - If we are a confined process, active is a refcounted copy
+ * of the profile that was on the subdomain at entry.
+ * This allows us to not have to hold a lock around
+ * all this code. If profile replacement has taken place
+ * our sd->active may not equal sd->active any more.
+ * This is okay since the operation is treated as if
+ * the transition occured before replacement.
+ *
+ * - If newprofile points to an actual profile (result of
+ * aa_profilelist_find above), this profile may have been
+ * replaced. We need to fix it up. Doing this to avoid
+ * having to hold a lock around all this code.
+ */
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ /* Determine if profile we found earlier is stale.
+ * If so, reobtain it. N.B stale flag should never be
+ * set on null_complain profile.
+ */
+ if (newprofile && unlikely(newprofile->isstale)) {
+ WARN_ON(newprofile == null_complain_profile);
+
+ /* drop refcnt obtained from earlier get_aaprofile */
+ put_aaprofile(newprofile);
+
+ newprofile = aa_profilelist_find(filename);
+
+ if (!newprofile) {
+ /* Race, profile was removed, not replaced.
+ * Redo with error checking
+ */
+ spin_unlock_irqrestore(&sd_lock, flags);
+ goto find_profile;
+ }
+ }
+
+ aa_switch(sd, newprofile);
+ put_aaprofile(newprofile);
+
+ if (complain && newprofile == null_complain_profile)
+ LOG_HINT(newprofile, GFP_ATOMIC, HINT_CHGPROF,
+ "pid=%d\n",
+ current->pid);
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+ }
+
+ aa_put_name(filename);
+
+ put_aaprofile(active);
+
+out:
+ return error;
+}
+
+/**
+ * aa_release - release the task's subdomain
+ * @p: task being released
+ *
+ * This is called after a task has exited and the parent has reaped it.
+ * @p->security blob is freed.
+ *
+ * This is the one case where we don't need to hold the sd_lock before
+ * removing a profile from a subdomain. Once the subdomain has been
+ * removed from the subdomain_list, we are no longer racing other writers.
+ * There may still be other readers so we must still use aa_switch
+ * to put the subdomain's reference safely.
+ */
+void aa_release(struct task_struct *p)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(p->security);
+ if (sd) {
+ p->security = NULL;
+
+ aa_subdomainlist_remove(sd);
+
+ aa_switch_unconfined(sd);
+
+ kfree(sd);
+ }
+}
+
+/*****************************
+ * global subprofile functions
+ ****************************/
+
+/**
+ * do_change_hat - actually switch hats
+ * @hat_name: name of hat to swtich to
+ * @sd: current subdomain
+ *
+ * Switch to a new hat. Return %0 on success, error otherwise.
+ */
+static inline int do_change_hat(const char *hat_name, struct subdomain *sd)
+{
+ struct aaprofile *sub;
+ int error = 0;
+
+ sub = __aa_find_profile(hat_name, &BASE_PROFILE(sd->active)->sub);
+
+ if (sub) {
+ /* change hat */
+ aa_switch(sd, sub);
+ put_aaprofile(sub);
+ } else {
+ /* There is no such subprofile change to a NULL profile.
+ * The NULL profile grants no file access.
+ *
+ * This feature is used by changehat_apache.
+ *
+ * N.B from the null-profile the task can still changehat back
+ * out to the parent profile (assuming magic != NULL)
+ */
+ if (SUBDOMAIN_COMPLAIN(sd)) {
+ LOG_HINT(sd->active, GFP_ATOMIC, HINT_UNKNOWN_HAT,
+ "%s pid=%d "
+ "profile=%s active=%s\n",
+ hat_name,
+ current->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+ } else {
+ AA_DEBUG("%s: Unknown hatname '%s'. "
+ "Changing to NULL profile "
+ "(%s(%d) profile %s active %s)\n",
+ __FUNCTION__,
+ hat_name,
+ current->comm, current->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+ error = -EACCES;
+ }
+ aa_switch(sd, sd->active->null_profile);
+ }
+
+ return error;
+}
+
+/**
+ * aa_change_hat - change hat to/from subprofile
+ * @hat_name: specifies hat to change to
+ * @hat_magic: token to validate hat change
+ *
+ * Change to new @hat_name when current hat is top level profile, and store
+ * the @hat_magic in the current subdomain. If the new @hat_name is
+ * %NULL, and the @hat_magic matches that stored in the current subdomain
+ * return to original top level profile. Returns %0 on success, error
+ * otherwise.
+ */
+int aa_change_hat(const char *hat_name, u32 hat_magic)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ int error = 0;
+
+ AA_DEBUG("%s: %p, 0x%x (pid %d)\n",
+ __FUNCTION__,
+ hat_name, hat_magic,
+ current->pid);
+
+ /* Dump out above debugging in WARN mode if we are in AUDIT mode */
+ if (SUBDOMAIN_AUDIT(sd)) {
+ AA_WARN("%s: %s, 0x%x (pid %d)\n",
+ __FUNCTION__, hat_name ? hat_name : "NULL",
+ hat_magic, current->pid);
+ }
+
+ /* check to see if an unconfined process is doing a changehat. */
+ if (!__aa_is_confined(sd)) {
+ error = -EACCES;
+ goto out;
+ }
+
+ /* Check whether current domain is parent
+ * or one of the sibling children
+ */
+ if (!IN_SUBPROFILE(sd->active)) {
+ /*
+ * parent
+ */
+ if (hat_name) {
+ AA_DEBUG("%s: switching to %s, 0x%x\n",
+ __FUNCTION__,
+ hat_name,
+ hat_magic);
+
+ /*
+ * N.B hat_magic == 0 has a special meaning
+ * this indicates that the task may never changehat
+ * back to it's parent, it will stay in this subhat
+ * (or null-profile, if the hat doesn't exist) until
+ * the task terminates
+ */
+ sd->hat_magic = hat_magic;
+ error = do_change_hat(hat_name, sd);
+ } else {
+ /* Got here via changehat(NULL, magic)
+ *
+ * We used to simply update the magic cookie.
+ * That's an odd behaviour, so just do nothing.
+ */
+ }
+ } else {
+ /*
+ * child -- check to make sure magic is same as what was
+ * passed when we switched into this profile,
+ * Handle special casing of NULL magic which confines task
+ * to subprofile and prohibits further changehats
+ */
+ if (hat_magic == sd->hat_magic && sd->hat_magic) {
+ if (!hat_name) {
+ /*
+ * Got here via changehat(NULL, magic)
+ * Return from subprofile, back to parent
+ */
+ aa_switch(sd, sd->active->parent);
+
+ /* Reset hat_magic to zero.
+ * New value will be passed on next changehat
+ */
+ sd->hat_magic = 0;
+ } else {
+ /* change to another (sibling) profile */
+ error = do_change_hat(hat_name, sd);
+ }
+ } else if (sd->hat_magic) {
+ AA_ERROR("KILLING process %s(%d) "
+ "Invalid change_hat() magic# 0x%x "
+ "(hatname %s profile %s active %s)\n",
+ current->comm, current->pid,
+ hat_magic,
+ hat_name ? hat_name : "NULL",
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ /* terminate current process */
+ (void)send_sig_info(SIGKILL, NULL, current);
+ } else { /* sd->hat_magic == NULL */
+ AA_ERROR("KILLING process %s(%d) "
+ "Task was confined to current subprofile "
+ "(profile %s active %s)\n",
+ current->comm, current->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ /* terminate current process */
+ (void)send_sig_info(SIGKILL, NULL, current);
+ }
+
+ }
+
+out:
+ return error;
+}
Arjan van de Ven
2006-04-19 18:10:30 UTC
Permalink
Post by Tony Jones
+/**
+ * _aa_perm_dentry
+ *
+ * Helper function. Obtain pathname for specified dentry.
which namespace will this be in?
Post by Tony Jones
Verify if profile
+ * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly
+ * necessary to search mountpoints in namespace -- when nameidata is passed
+ * more fully, this code can go away). If more than one mountpoint matches
+ * but none satisfy the profile, only the first pathname (mountpoint) is
+ * returned for subsequent logging.
that sounds too bad ;)
If I manage to mount /etc/passwd as /tmp/passwd, you'll only find the
later and your entire security system seems to be down the drain.
Post by Tony Jones
+/**
+ * aa_register - register a new program
+ *
+ * Try to register a new program during execve(). This should give the
+ * new program a valid subdomain.
+ */
+int aa_register(struct file *filp)
+{
+ char *filename;
+ struct subdomain *sd;
+ struct aaprofile *active;
+ struct aaprofile *newprofile = NULL, unconstrained_flag;
+ int error = -ENOMEM,
+ exec_mode = 0,
+ find_profile = 0,
+ find_profile_mandatory = 0,
+ complain = 0;
+
+ AA_DEBUG("%s\n", __FUNCTION__);
+
+ sd = AA_SUBDOMAIN(current->security);
+
+ if (sd) {
+ complain = SUBDOMAIN_COMPLAIN(sd);
+ } else {
+ /* task has no subdomain. This can happen when a task is
+ * created when subdomain is not loaded. Allocate and
+ * attach a subdomain to the task
+ */
+ sd = alloc_subdomain(current);
+ if (!sd) {
+ AA_WARN("%s: Failed to allocate subdomain\n",
+ __FUNCTION__);
+ goto out;
+ }
+
+ current->security = sd;
+ }
+
+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt);
what if filp->f_dentry is NULL ?
like when the file got unlinked under you?


-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Crispin Cowan
2006-04-19 18:57:27 UTC
Permalink
Post by Arjan van de Ven
Post by Tony Jones
Verify if profile
+ * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly
+ * necessary to search mountpoints in namespace -- when nameidata is passed
+ * more fully, this code can go away). If more than one mountpoint matches
+ * but none satisfy the profile, only the first pathname (mountpoint) is
+ * returned for subsequent logging.
that sounds too bad ;)
If I manage to mount /etc/passwd as /tmp/passwd, you'll only find the
later and your entire security system seems to be down the drain.
If you are a confined process, then you don't get to mount things, for
this reason, among others.

Crispin
--
Crispin Cowan, Ph.D. http://crispincowan.com/~crispin/
Director of Software Engineering, Novell http://novell.com

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Rik van Riel
2006-04-19 23:05:08 UTC
Permalink
Post by Crispin Cowan
Post by Arjan van de Ven
that sounds too bad ;)
If I manage to mount /etc/passwd as /tmp/passwd, you'll only find the
later and your entire security system seems to be down the drain.
If you are a confined process, then you don't get to mount things, for
this reason, among others.
Are confined processes always restricted from starting
non-confined processes?
--
All Rights Reversed
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Seth Arnold
2006-04-19 23:18:31 UTC
Permalink
Post by Rik van Riel
Are confined processes always restricted from starting
non-confined processes?
It is specified in policy via an unconstrained execution flag: 'ux'. Any
unconfined children can of course do whatever they wish.
Rik van Riel
2006-04-19 23:21:52 UTC
Permalink
Post by Seth Arnold
Post by Rik van Riel
Are confined processes always restricted from starting
non-confined processes?
It is specified in policy via an unconstrained execution flag: 'ux'. Any
unconfined children can of course do whatever they wish.
And the default is for the children to inherit the security
policy from the parent process, like in SELinux ?

How do apparmor and selinux differ in how they contain bad
things?
--
All Rights Reversed
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Crispin Cowan
2006-04-19 23:50:48 UTC
Permalink
Post by Rik van Riel
Post by Seth Arnold
Post by Rik van Riel
Are confined processes always restricted from starting
non-confined processes?
It is specified in policy via an unconstrained execution flag: 'ux'. Any
unconfined children can of course do whatever they wish.
And the default is for the children to inherit the security
policy from the parent process, like in SELinux ?
How do apparmor and selinux differ in how they contain bad
things?
To be able to execute any child, the confined process must have explicit
permission to execute it:

* "/bin/foo px" says that the child will execute with its own
policy. The policy must exist, or access is denied. This is useful
if, say, xinetd wants to exec Sendmail.
* "/bin/foo ix" says that the child will execute with its parent's
policy, "inherit". This is useful if, say, a shell script wants to
exec cp.
* "/bin/foo ux" says that the child will exec with no confinement at
all. This should be used carefully, say, if sshd wants to exec
bash to allow an administrator to have an unconfined shell.

You can also say something like "/bin/** ix" which would let you run
anything in /bin, but all subject to the parent's policy. You could say
"/bin/** px" but that would mostly cause exec() failures except to the
extent that policies exist. You could say "/bin/** ux" but that would
not be wise :)

Crispin
--
Crispin Cowan, Ph.D. http://crispincowan.com/~crispin/
Director of Software Engineering, Novell http://novell.com

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-20 12:33:13 UTC
Permalink
Post by Crispin Cowan
Post by Arjan van de Ven
Post by Tony Jones
Verify if profile
+ * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly
+ * necessary to search mountpoints in namespace -- when nameidata is passed
+ * more fully, this code can go away). If more than one mountpoint matches
+ * but none satisfy the profile, only the first pathname (mountpoint) is
+ * returned for subsequent logging.
that sounds too bad ;)
If I manage to mount /etc/passwd as /tmp/passwd, you'll only find the
later and your entire security system seems to be down the drain.
If you are a confined process, then you don't get to mount things, for
this reason, among others.
Which is an example of the brokenness of the security model - its
fragileness in the face of manipulation of the file tree leads to
inflexibility. So for example, if you wanted to _protect_ a process
that does mount things as part of its legitimate purpose, e.g. by
limiting what it can access to prevent it from taking untrustworthy
inputs, then you are out of luck - it is either confined and can't mount
or not confined and can mount.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Lars Marowsky-Bree
2006-04-20 16:27:50 UTC
Permalink
Post by Stephen Smalley
Post by Crispin Cowan
If you are a confined process, then you don't get to mount things, for
this reason, among others.
Which is an example of the brokenness of the security model - its
fragileness in the face of manipulation of the file tree leads to
inflexibility.
Now, now. Not every _limitation_ translates to _brokenness_. Some of
them are simply that - limitations. If you no like, you no run that
particular solution.
--
High Availability & Clustering
SUSE Labs, Research and Development
SUSE LINUX Products GmbH - A Novell Business -- Charles Darwin
"Ignorance more frequently begets confidence than does knowledge"

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 17:39:32 UTC
Permalink
Post by Arjan van de Ven
Post by Tony Jones
+/**
+ * _aa_perm_dentry
+ *
+ * Helper function. Obtain pathname for specified dentry.
which namespace will this be in?
We perform validation relative to the current tasks namespace
Post by Arjan van de Ven
Post by Tony Jones
Verify if profile
+ * authorizes mask operations on pathname (due to lack of vfsmnt it is sadly
+ * necessary to search mountpoints in namespace -- when nameidata is passed
+ * more fully, this code can go away). If more than one mountpoint matches
+ * but none satisfy the profile, only the first pathname (mountpoint) is
+ * returned for subsequent logging.
that sounds too bad ;)
If I manage to mount /etc/passwd as /tmp/passwd, you'll only find the
later and your entire security system seems to be down the drain.
Well first off, as addressed elsewhere, a confined task cannot mount.

Also, if you do bind mount as above, we will find both but we only report
the first. The path reported in this event will be used to generate policy.
Clearly if the ordering of which entry is found first varies, then for a
subsequent iteration, the policy may not be sufficient to grant the task
access and it will likely fail. This is important, a failure to specify a
path in a profile results in a failure of access not a allowal of access.
Solution, include all paths in the profile confining the task.

I do grant that a situation of very temporal mounts can make the generation
of a sane profile difficult. I've not used namespaces in such a manner so
I've not seen exactly how bad it is. One of my hopes from this thread was
that people would post real life in the trenches with namespaces examples,
both to aid us and maybe to illustrate where our approach is broken. Likewise
if you generate policy in an environment with a large number of mounts which
will not esist when the task runs under policy enforcement, you could run into
issues.
Post by Arjan van de Ven
Post by Tony Jones
+ filename = aa_get_name(filp->f_dentry, filp->f_vfsmnt);
what if filp->f_dentry is NULL ?
like when the file got unlinked under you?
I believe there is a misunderstanding about the value of a unhashed dentry
when a file is unlinked from under a task. I responded to a more detailed
version of the same question posted by Valdis Kletniek.

Thanks for the post.

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Jan Engelhardt
2006-04-19 19:32:16 UTC
Permalink
Post by Tony Jones
+/**
+ * _aa_perm_dentry
[...]
Post by Tony Jones
+ * Return %0 (success), +ve (mask of permissions not satisfied) or -ve (system
+ * error, most likely -%ENOMEM).
+ */
This was probably meant to read %-ENOMEM. (Applies anywhere else too!)



Jan Engelhardt
--
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-19 19:50:43 UTC
Permalink
Post by Tony Jones
+/**
+ * aa_get_name - retrieve fully qualified path name
+ *
+ * Returns fully qualified path name on sucess, NULL on failure.
+ * aa_put_name must be used to free allocated buffer.
+ */
+char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt)
+{
+ char *page, *name = NULL;
+
+ page = (char *)__get_free_page(GFP_KERNEL);
+ if (!page)
+ goto out;
+
+ name = d_path_flags(dentry, mnt, page, PAGE_SIZE,
+ DPATH_SYSROOT|DPATH_NODELETED);
So on every inode hook call, you end up allocating a temporary page,
calling d_path (taking global dcache_lock), and you do this possibly
multiple times per object (due to iterating over vfsmounts) and you may
need to do it for multiple objects on a single hook call (e.g.
link/rename). Is that correct?
Post by Tony Jones
+/**
+ * aa_perm_nameidata: interface to sd_perm accepting nameidata
+ */
+int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd, int mask)
+{
+ int error = 0;
+
+ if (nd)
+ error = aa_perm(active, nd->dentry, nd->mnt, mask);
+
+ return error;
+}
So what about the !nd case. For when permission(9) is called with a
NULL nameidata. Unconditional success in that case seems a bit
worrisome.

I also vaguely recall a problem with trying to use the nameidata
(vfsmount, dentry) pair to d_path in SELinux for audit purposes back
when avc_audit was trying to audit paths before migrating to using the
audit system for that purpose. Interacted badly with rpc_pipefs upon
rpc_lookup_parent, IIRC. Might want to check whether you handle it
correctly.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Al Viro
2006-04-20 09:40:36 UTC
Permalink
Post by Tony Jones
+static int _aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
+ int mask, const char **pname)
+{
+ char *name = NULL, *failed_name = NULL;
+ struct aa_path_data data;
+ int error = 0, failed_error = 0, path_error,
+ complain = PROFILE_COMPLAIN(active);
+
+ /* search all paths to dentry */
+
+ aa_path_begin(dentry, &data);
+ do {
+ name = aa_path_getname(&data);
+ if (name) {
+ /* error here is 0 (success) or +ve (mask of perms) */
+ error = aa_file_perm(active, name, mask);
+
+ /* access via any path is enough */
+ if (complain || error == 0)
+ break; /* Caller must free name */
+
+ /* Already have an path that failed? */
+ if (failed_name) {
+ aa_put_name(name);
+ } else {
+ failed_name = name;
+ failed_error = error;
+ }
+ }
+ } while (name);
Is that a joke? Are you really proposing to do _that_ on anything resembling
a hot path?

BTW, the problems here really have nothing to do with namespaces or
lazy umount, seeing that it's whitelisting. Moderate amount of bindings
will kill you here. So much that I suspect that one-time overhead of
creating a namespace and umounting / remounting noexec / etc. on
execve() will be cheaper than all this crap.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Serge E. Hallyn
2006-04-20 11:40:10 UTC
Permalink
Post by Al Viro
Post by Tony Jones
+static int _aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
+ int mask, const char **pname)
+{
+ char *name = NULL, *failed_name = NULL;
+ struct aa_path_data data;
+ int error = 0, failed_error = 0, path_error,
+ complain = PROFILE_COMPLAIN(active);
+
+ /* search all paths to dentry */
+
+ aa_path_begin(dentry, &data);
+ do {
+ name = aa_path_getname(&data);
+ if (name) {
+ /* error here is 0 (success) or +ve (mask of perms) */
+ error = aa_file_perm(active, name, mask);
+
+ /* access via any path is enough */
+ if (complain || error == 0)
+ break; /* Caller must free name */
+
+ /* Already have an path that failed? */
+ if (failed_name) {
+ aa_put_name(name);
+ } else {
+ failed_name = name;
+ failed_error = error;
+ }
+ }
+ } while (name);
Is that a joke? Are you really proposing to do _that_ on anything resembling
a hot path?
BTW, the problems here really have nothing to do with namespaces or
lazy umount, seeing that it's whitelisting. Moderate amount of bindings
will kill you here. So much that I suspect that one-time overhead of
creating a namespace and umounting / remounting noexec / etc. on
execve() will be cheaper than all this crap.
I guess this would require per-vfsmount flags (i.e. mount --bind -o ro)
to be implemented, but IIUC the suggestion is

given a policy

/bin/stty {
/bin/stty r
}

during execve AA would unshare(CLONE_NEWNS), remount / readonly and
noexec, and mount /bin/stty into place with exec privs. I guess
getting /bin/stty into place shouldn't be much of a challenge (i.e.
just do the operations in the order
mkdir /.tmp123
mount --bind -o ro,noexec / /.tmp123
mount --bind /bin/stty /.tmp123/bin/stty
mount --bind /.tmp123 /
)
but implementing the 'ux' exec permission which apparmor currently has
(i.e. giving the ability for stty to then execute /bin/login without
restrictions) could be more challenging.

This also might beg for sys_unshare() (and corresponding code in clone)
to have it's own security_vfs_unshare() hook, rather than being globbed
in with CAP_SYS_ADMIN.

-serge
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 21:39:55 UTC
Permalink
Post by Serge E. Hallyn
Post by Al Viro
Post by Tony Jones
+static int _aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
+ int mask, const char **pname)
+{
+ char *name = NULL, *failed_name = NULL;
+ struct aa_path_data data;
+ int error = 0, failed_error = 0, path_error,
+ complain = PROFILE_COMPLAIN(active);
+
+ /* search all paths to dentry */
+
+ aa_path_begin(dentry, &data);
+ do {
+ name = aa_path_getname(&data);
+ if (name) {
+ /* error here is 0 (success) or +ve (mask of perms) */
+ error = aa_file_perm(active, name, mask);
+
+ /* access via any path is enough */
+ if (complain || error == 0)
+ break; /* Caller must free name */
+
+ /* Already have an path that failed? */
+ if (failed_name) {
+ aa_put_name(name);
+ } else {
+ failed_name = name;
+ failed_error = error;
+ }
+ }
+ } while (name);
Is that a joke? Are you really proposing to do _that_ on anything resembling
a hot path?
Unfortunately Al, no it's not a joke. We've been asked to publish performance
numbers by Serge as part of another thread. We plan to do so shortly. Of
course results are likely going to be related to the complexity of the
namespace the benchmark operates within. Suggestions of benchmarks that
significantly exercise namespaces are more than welcome.

We are no fan of this code either but the fact is that vfsmounts are passed
inconsistently to the LSM. Of course this isn't an issue of LSM just not
taking available data, rather of the information not being available in the
VFS at the point the hook is invoked. Going out on a limb here, to fully
support read-only bind mounts would seem to require similar changes - but
with a more limited scope - cases like security_inode_create and
security_inode_link likely still wouldn't have the necessary information to
fully eliminate the above fuglyness. Perhaps one hook cannot be made to provide
both useful inode and name information.
Post by Serge E. Hallyn
Post by Al Viro
BTW, the problems here really have nothing to do with namespaces or
lazy umount, seeing that it's whitelisting. Moderate amount of bindings
will kill you here. So much that I suspect that one-time overhead of
creating a namespace and umounting / remounting noexec / etc. on
execve() will be cheaper than all this crap.
I guess this would require per-vfsmount flags (i.e. mount --bind -o ro)
to be implemented, but IIUC the suggestion is
given a policy
/bin/stty {
/bin/stty r
}
during execve AA would unshare(CLONE_NEWNS), remount / readonly and
noexec, and mount /bin/stty into place with exec privs. I guess
getting /bin/stty into place shouldn't be much of a challenge (i.e.
just do the operations in the order
mkdir /.tmp123
mount --bind -o ro,noexec / /.tmp123
mount --bind /bin/stty /.tmp123/bin/stty
mount --bind /.tmp123 /
)
but implementing the 'ux' exec permission which apparmor currently has
(i.e. giving the ability for stty to then execute /bin/login without
restrictions) could be more challenging.
This also might beg for sys_unshare() (and corresponding code in clone)
to have it's own security_vfs_unshare() hook, rather than being globbed
in with CAP_SYS_ADMIN.
Are we referring here to the idea of giving each confined task it's own
namespace upon exec? An interesting idea for sure. The exec portion you
mention above is pretty trivial. How to handle directories, scratch space
(the ability of a confined task to write selected temp files) is less clear.
Also one of the most powerful aspects of AppArmor (at least if the users are
to be believed :-) is the ability for policy to contain path name expansion
(globbing). For instance, it is very useful to grant one web application
access to /var/www/**.html and another access to /var/www/**.pl.

But I think passing vfsmounts fully into LSM and closing the cases where a
nameidata can be NULL is an alternative plan B. Something we are willing to
put effort into helping achieve.

I believe what users want is a system which offers good practical security
together with ease of expressiveness in policy (so that it may be actually
maintained by other than distribution vendors). We strongly believe that
AppArmor provides this and think it is important to persue changes to LSM
(and the VFS) as necessary. However there is clearly an undeniable elegance
to the per-confined-task namespace idea. I have my concerns about whether it
can achieve close to the same expressiveness as current AppArmor policy (one
of AppArmor's clear advantages over SELinux) but it is clearly important that
the namespace idea is explored. Just not to the exclusion of also exploring
rework of the LSM/VFS.

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:49:46 UTC
Permalink
This patch implements the AppArmor file structure underneath securityfs.
Securityfs is normally mounted as /sys/kernel/security

The following files are created under /sys/kernel/security/apparmor
control
audit - Controls the global setting for auditing all
accesses.
complain - Controls the global setting for learning mode
(usually this is set per profile rather than
globally)
debug - Controls whether debugging is enabled.
This needs to be made more fine grained
logsyscall - Controls whether when logging to the audit
subsystem full syscall auditing is enabled.

The values by default for all of the above are 0.

matching - Returns the features of the installed matching submodule
profiles - Returns the profiles currently loaded and for each whether
it is in complain (learning) or enforce mode.
.load
.remove
.replace - Used by userspace tools to load, remove and replace new
profiles.


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/apparmorfs.c | 432 +++++++++++++++++++++++++++++++++++++++++
1 files changed, 432 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/apparmorfs.c
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor filesystem (part of securityfs)
+ */
+
+#include <linux/security.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <asm/uaccess.h>
+
+#include "apparmor.h"
+#include "inline.h"
+#include "match/match.h"
+
+#define SECFS_AA "apparmor"
+static struct dentry *aafs_dentry = NULL;
+
+/* profile */
+extern struct seq_operations apparmorfs_profiles_op;
+static int aa_prof_open(struct inode *inode, struct file *file);
+static int aa_prof_release(struct inode *inode, struct file *file);
+
+static struct file_operations apparmorfs_profiles_fops = {
+ .open = aa_prof_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = aa_prof_release,
+};
+
+/* matching */
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos);
+
+static struct file_operations apparmorfs_matching_fops = {
+ .read = aa_matching_read,
+};
+
+
+/* interface */
+static ssize_t aa_profile_load(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos);
+static ssize_t aa_profile_replace(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos);
+static ssize_t aa_profile_remove(struct file *f, const char __user *buf,
+ size_t size, loff_t *pos);
+
+static struct file_operations apparmorfs_profile_load = {
+ .write = aa_profile_load
+};
+
+static struct file_operations apparmorfs_profile_replace = {
+ .write = aa_profile_replace
+};
+
+static struct file_operations apparmorfs_profile_remove = {
+ .write = aa_profile_remove
+};
+
+
+/* control */
+static u64 aa_control_get(void *data);
+static void aa_control_set(void *data, u64 val);
+
+DEFINE_SIMPLE_ATTRIBUTE(apparmorfs_control_fops, aa_control_get,
+ aa_control_set, "%lld\n");
+
+
+
+/* table of static entries */
+
+static struct root_entry {
+ const char *name;
+ int mode;
+ int access;
+ struct file_operations *fops;
+ void *data;
+
+ /* internal fields */
+ struct dentry *dentry;
+ int parent_index;
+} root_entries[] = {
+ /* our root, normally /sys/kernel/security/apparmor */
+ {SECFS_AA, S_IFDIR, 0550}, /* DO NOT EDIT/MOVE */
+
+ /* interface for obtaining list of profiles currently loaded */
+ {"profiles", S_IFREG, 0440, &apparmorfs_profiles_fops,
+ NULL},
+
+ /* interface for obtaining matching features supported */
+ {"matching", S_IFREG, 0440, &apparmorfs_matching_fops,
+ NULL},
+
+ /* interface for loading/removing/replacing profiles */
+ {".load", S_IFREG, 0640, &apparmorfs_profile_load,
+ NULL},
+ {".replace", S_IFREG, 0640, &apparmorfs_profile_replace,
+ NULL},
+ {".remove", S_IFREG, 0640, &apparmorfs_profile_remove,
+ NULL},
+
+ /* interface for setting binary config values */
+ {"control", S_IFDIR, 0550},
+ {"complain", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_complain},
+ {"audit", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_audit},
+ {"debug", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_debug},
+ {"logsyscall", S_IFREG, 0640, &apparmorfs_control_fops,
+ &apparmor_logsyscall},
+ {NULL, S_IFDIR, 0},
+
+ /* root end */
+ {NULL, S_IFDIR, 0}
+};
+
+#define AAFS_DENTRY root_entries[0].dentry
+
+static const unsigned int num_entries =
+ sizeof(root_entries) / sizeof(struct root_entry);
+
+
+
+static int aa_prof_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &apparmorfs_profiles_op);
+}
+
+
+static int aa_prof_release(struct inode *inode, struct file *file)
+{
+ return seq_release(inode, file);
+}
+
+static ssize_t aa_matching_read(struct file *file, char __user *buf,
+ size_t size, loff_t *ppos)
+{
+ const char *matching = aamatch_features();
+
+ return simple_read_from_buffer(buf, size, ppos, matching,
+ strlen(matching));
+}
+
+static char *aa_simple_write_to_buffer(const char __user *userbuf,
+ size_t alloc_size, size_t copy_size,
+ loff_t *pos, const char *msg)
+{
+ struct aaprofile *active;
+ 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.
+ */
+ rcu_read_lock();
+ active = get_activeptr_rcu();
+ if (active) {
+ AA_WARN("REJECTING access to profile %s (%s(%d) "
+ "profile %s active %s)\n",
+ msg, current->comm, current->pid,
+ BASE_PROFILE(active)->name, active->name);
+
+ data = ERR_PTR(-EPERM);
+ goto out;
+ }
+ rcu_read_unlock();
+
+ 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;
+}
+
+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, "load");
+
+ if (!IS_ERR(data)) {
+ error = aa_file_prof_add(data, size);
+ vfree(data);
+ } else {
+ error = PTR_ERR(data);
+ }
+
+ return error;
+}
+
+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, "replacement");
+
+ if (!IS_ERR(data)) {
+ error = aa_file_prof_repl(data, size);
+ vfree(data);
+ } else {
+ error = PTR_ERR(data);
+ }
+
+ return error;
+}
+
+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_file_prof_remove needs a null terminated string so 1 extra
+ * byte is allocated and null the copied data is then null terminated
+ */
+ data = aa_simple_write_to_buffer(buf, size+1, size, pos, "removal");
+
+ if (!IS_ERR(data)) {
+ data[size] = 0;
+ error = aa_file_prof_remove(data, size);
+ vfree(data);
+ } else {
+ error = PTR_ERR(data);
+ }
+
+ return error;
+}
+
+static u64 aa_control_get(void *data)
+{
+ return *(int *)data;
+}
+
+static void aa_control_set(void *data, u64 val)
+{
+ if (val > 1)
+ val = 1;
+
+ *(int*)data = (int)val;
+}
+
+static void clear_apparmorfs(void)
+{
+ unsigned int i;
+
+ for (i=0; i < num_entries;i++) {
+ unsigned int index;
+
+ if (root_entries[i].mode == S_IFDIR) {
+ if (root_entries[i].name)
+ /* defer dir free till all sub-entries freed */
+ continue;
+ else
+ /* cleanup parent */
+ index = root_entries[i].parent_index;
+ } else {
+ index = i;
+ }
+
+ if (root_entries[index].dentry) {
+ securityfs_remove(root_entries[index].dentry);
+
+ AA_DEBUG("%s: deleted apparmorfs entry name=%s "
+ "dentry=%p\n",
+ __FUNCTION__,
+ root_entries[index].name,
+ root_entries[index].dentry);
+
+ root_entries[index].dentry = NULL;
+ root_entries[index].parent_index = 0;
+ }
+ }
+}
+
+static int populate_apparmorfs(struct dentry *root)
+{
+ unsigned int i, parent_index, depth;
+
+ for (i = 0; i < num_entries; i++) {
+ root_entries[i].dentry = NULL;
+ root_entries[i].parent_index = 0;
+ }
+
+ /* 1. Verify entry 0 is valid [sanity check] */
+ if (num_entries == 0 ||
+ !root_entries[0].name ||
+ strcmp(root_entries[0].name, SECFS_AA) != 0 ||
+ root_entries[0].mode != S_IFDIR) {
+ AA_ERROR("%s: root entry 0 is not SECFS_AA/dir\n",
+ __FUNCTION__);
+ goto error;
+ }
+
+ /* 2. Build back pointers */
+ parent_index = 0;
+ depth = 1;
+
+ for (i = 1; i < num_entries; i++) {
+ root_entries[i].parent_index = parent_index;
+
+ if (root_entries[i].name &&
+ root_entries[i].mode == S_IFDIR) {
+ depth++;
+ parent_index = i;
+ } else if (!root_entries[i].name) {
+ if (root_entries[i].mode != S_IFDIR || depth == 0) {
+ AA_ERROR("%s: root_entry %d invalid (%u %d)",
+ __FUNCTION__, i,
+ root_entries[i].mode,
+ root_entries[i].parent_index);
+ goto error;
+ }
+
+ depth--;
+ parent_index = root_entries[parent_index].parent_index;
+ }
+ }
+
+ if (depth != 0) {
+ AA_ERROR("%s: root_entry table not correctly terminated\n",
+ __FUNCTION__);
+ goto error;
+ }
+
+ /* 3. Create root (parent=NULL) */
+ root_entries[0].dentry = securityfs_create_file(
+ root_entries[0].name,
+ root_entries[0].mode |
+ root_entries[0].access,
+ NULL, NULL, NULL);
+
+ if (IS_ERR(root_entries[0].dentry))
+ goto error;
+ else
+ AA_DEBUG("%s: created securityfs/apparmor [dentry=%p]\n",
+ __FUNCTION__, root_entries[0].dentry);
+
+
+ /* 4. create remaining nodes */
+ for (i = 1; i < num_entries; i++) {
+ struct dentry *parent;
+ void *data = NULL;
+ struct file_operations *fops = NULL;
+
+ /* end of directory ? */
+ if (!root_entries[i].name)
+ continue;
+
+ parent = root_entries[root_entries[i].parent_index].dentry;
+
+ if (root_entries[i].mode != S_IFDIR) {
+ data = root_entries[i].data;
+ fops = root_entries[i].fops;
+ }
+
+ root_entries[i].dentry = securityfs_create_file(
+ root_entries[i].name,
+ root_entries[i].mode |
+ root_entries[i].access,
+ parent,
+ data,
+ fops);
+
+ if (IS_ERR(root_entries[i].dentry))
+ goto cleanup_error;
+
+ AA_DEBUG("%s: added apparmorfs entry "
+ "name=%s mode=%x dentry=%p [parent %p]\n",
+ __FUNCTION__, root_entries[i].name,
+ root_entries[i].mode|root_entries[i].access,
+ root_entries[i].dentry, parent);
+ }
+
+ return 0;
+
+cleanup_error:
+ clear_apparmorfs();
+
+error:
+ return -EINVAL;
+}
+
+int create_apparmorfs(void)
+{
+ int error = 0;
+
+ if (AAFS_DENTRY) {
+ error = -EEXIST;
+ AA_ERROR("%s: Subdomain securityfs already exists\n",
+ __FUNCTION__);
+ } else {
+ error = populate_apparmorfs(aafs_dentry);
+ if (error != 0) {
+ AA_ERROR("%s: Error populating Subdomain securityfs\n",
+ __FUNCTION__);
+ }
+ }
+
+ return error;
+}
+
+void destroy_apparmorfs(void)
+{
+ if (AAFS_DENTRY)
+ clear_apparmorfs();
+}
Amy Griffis
2006-04-21 21:13:51 UTC
Permalink
Tony Jones wrote: [Wed Apr 19 2006, 01:49:46PM EDT]
Post by Tony Jones
This patch implements the AppArmor file structure underneath securityfs.
Securityfs is normally mounted as /sys/kernel/security
The following files are created under /sys/kernel/security/apparmor
control
audit - Controls the global setting for auditing all
accesses.
complain - Controls the global setting for learning mode
(usually this is set per profile rather than
globally)
debug - Controls whether debugging is enabled.
This needs to be made more fine grained
logsyscall - Controls whether when logging to the audit
subsystem full syscall auditing is enabled.
Why not use audit's audit_enabled toggle instead? This would
eliminate the overhead of data collection for syscall auditing, in
addition to eliminating the extra log data.

Is it likely that a user will want to keep syscall auditing on for
some applications, while having it disabled for AppArmor's use?
Post by Tony Jones
The values by default for all of the above are 0.
matching - Returns the features of the installed matching submodule
profiles - Returns the profiles currently loaded and for each whether
it is in complain (learning) or enforce mode.
.load
.remove
.replace - Used by userspace tools to load, remove and replace new
profiles.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:49:21 UTC
Permalink
This patch provides the various common headerfiles used by the AppArmor module.

apparmor.h contains the core data structures.
shared.h contains definitions that are common to the userspace policy loader.
inline.h implements various inline utility functions


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/apparmor.h | 325 +++++++++++++++++++++++++++++++++++++++++
security/apparmor/inline.h | 333 +++++++++++++++++++++++++++++++++++++++++++
security/apparmor/shared.h | 41 +++++
3 files changed, 699 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/apparmor.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 1998-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor internal prototypes
+ */
+
+#ifndef __SUBDOMAIN_H
+#define __SUBDOMAIN_H
+
+#include <linux/fs.h> /* Include for defn of iattr */
+#include <linux/rcupdate.h>
+
+#include "shared.h"
+
+/* Control parameters (0 or 1), settable thru module/boot flags or
+ * via /sys/kernel/security/apparmor/control */
+extern int apparmor_complain;
+extern int apparmor_debug;
+extern int apparmor_audit;
+extern int apparmor_logsyscall;
+
+/* PIPEFS_MAGIC */
+#include <linux/pipe_fs_i.h>
+/* from net/socket.c */
+#define SOCKFS_MAGIC 0x534F434B
+/* from inotify.c */
+#define INOTIFYFS_MAGIC 0xBAD1DEA
+
+#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \
+ (inode)->i_sb->s_magic != SOCKFS_MAGIC && \
+ (inode)->i_sb->s_magic != INOTIFYFS_MAGIC)
+
+#define PROFILE_COMPLAIN(_profile) \
+ (apparmor_complain == 1 || ((_profile) && (_profile)->flags.complain))
+
+#define SUBDOMAIN_COMPLAIN(_sd) \
+ (apparmor_complain == 1 || \
+ ((_sd) && (_sd)->active && (_sd)->active->flags.complain))
+
+#define PROFILE_AUDIT(_profile) \
+ (apparmor_audit == 1 || ((_profile) && (_profile)->flags.audit))
+
+#define SUBDOMAIN_AUDIT(_sd) \
+ (apparmor_audit == 1 || \
+ ((_sd) && (_sd)->active && (_sd)->active->flags.audit))
+
+/*
+ * 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 (apparmor_debug) \
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
+ } while (0)
+#define AA_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args)
+#define AA_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args)
+#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
+
+/* basic AppArmor data structures */
+
+struct flagval {
+ int debug;
+ int complain;
+ int audit;
+};
+
+enum entry_match_type {
+ aa_entry_literal,
+ aa_entry_tailglob,
+ aa_entry_pattern,
+ aa_entry_invalid
+};
+
+/* struct aa_entry - file ACL *
+ * @filename: filename controlled by this ACL
+ * @mode: permissions granted by ACL
+ * @type: type of match to perform against @filename
+ * @extradata: any extra data needed by an extended matching type
+ * @list: list the ACL is on
+ * @listp: permission partitioned lists this ACL is on.
+ *
+ * Each entry describes a file and an allowed access mode.
+ */
+struct aa_entry {
+ char *filename;
+ int mode; /* mode is 'or' of READ, WRITE, EXECUTE,
+ * INHERIT, UNCONSTRAINED, and LIBRARY
+ * (meaning don't prefetch). */
+
+ enum entry_match_type type;
+ void *extradata;
+
+ struct list_head list;
+ struct list_head listp[POS_AA_FILE_MAX + 1];
+};
+
+#define AA_EXEC_MODIFIER_MASK(mask) ((mask) & (AA_EXEC_UNCONSTRAINED |\
+ AA_EXEC_INHERIT |\
+ AA_EXEC_PROFILE))
+
+#define AA_EXEC_MASK(mask) ((mask) & (AA_MAY_EXEC |\
+ AA_EXEC_UNCONSTRAINED |\
+ AA_EXEC_INHERIT |\
+ AA_EXEC_PROFILE))
+
+
+/* struct aaprofile - basic confinement data
+ * @parent: non refcounted pointer to parent profile
+ * @name: the profiles name
+ * @file_entry: file ACL
+ * @file_entryp: vector of file ACL by permission granted
+ * @list: list this profile is on
+ * @sub: profiles list of subprofiles (HATS)
+ * @flags: flags controlling profile behavior
+ * @null_profile: if needed per profile learning and null confinement profile
+ * @isstale: flag to indicate the profile is stale
+ * @num_file_entries: number of file entries the profile contains
+ * @num_file_pentries: number of file entries for each partitioned list
+ * @capabilities: capabilities granted by the process
+ * @rcu: rcu head used when freeing the profile
+ * @count: reference count of the profile
+ *
+ * The AppArmor profile contains the basic confinement data. Each profile
+ * has a name and potentially a list of profile entries. The profiles are
+ * connected in a list
+ */
+struct aaprofile {
+ struct aaprofile *parent;
+ char *name;
+
+ struct list_head file_entry;
+ struct list_head file_entryp[POS_AA_FILE_MAX + 1];
+ struct list_head list;
+ struct list_head sub;
+ struct flagval flags;
+ struct aaprofile *null_profile;
+ int isstale;
+
+ int num_file_entries;
+ int num_file_pentries[POS_AA_FILE_MAX + 1];
+
+ kernel_cap_t capabilities;
+
+ struct rcu_head rcu;
+
+ struct kref count;
+};
+
+/**
+ * struct subdomain - primary label for confined tasks
+ * @active: the current active profile
+ * @hat_magic: the magic token controling the ability to leave a hat
+ * @list: list this subdomain is on
+ * @task: task that the subdomain confines
+ *
+ * Contains the tasks current active profile (which could change due to
+ * change_hat). Plus the hat_magic needed during change_hat.
+ *
+ * N.B AppArmor's previous product name SubDomain was derived from the name
+ * of this structure/concept (changehat reducing a task into a sub-domain).
+ */
+struct subdomain {
+ struct aaprofile *active; /* The current active profile */
+ u32 hat_magic; /* used with change_hat */
+ struct list_head list; /* list of subdomains */
+ struct task_struct *task;
+};
+
+typedef int (*aa_iter) (struct subdomain *, void *);
+
+/* aa_path_data
+ * temp (cookie) data used by aa_path_* functions, see inline.h
+ */
+struct aa_path_data {
+ struct dentry *root, *dentry;
+ struct namespace *namespace;
+ struct list_head *head, *pos;
+ int errno;
+};
+
+#define AA_SUBDOMAIN(sec) ((struct subdomain*)(sec))
+#define AA_PROFILE(sec) ((struct aaprofile*)(sec))
+
+/* Lock protecting access to 'struct subdomain' accesses */
+extern spinlock_t sd_lock;
+
+extern struct aaprofile *null_complain_profile;
+
+/* 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 {
+ unsigned short type, flags;
+ unsigned int result;
+ unsigned int gfp_mask;
+ int error_code;
+
+ const char *name;
+ unsigned int ival;
+ union {
+ const void *pval;
+ va_list vaval;
+ };
+};
+
+/* audit types */
+#define AA_AUDITTYPE_FILE 1
+#define AA_AUDITTYPE_DIR 2
+#define AA_AUDITTYPE_ATTR 3
+#define AA_AUDITTYPE_XATTR 4
+#define AA_AUDITTYPE_LINK 5
+#define AA_AUDITTYPE_CAP 6
+#define AA_AUDITTYPE_MSG 7
+#define AA_AUDITTYPE_SYSCALL 8
+#define AA_AUDITTYPE__END 9
+
+/* audit flags */
+#define AA_AUDITFLAG_AUDITSS_SYSCALL 1 /* log syscall context */
+#define AA_AUDITFLAG_LOGERR 2 /* log operations that failed due to
+ non permission errors */
+
+#define HINT_UNKNOWN_HAT "unknown_hat"
+#define HINT_FORK "fork"
+#define HINT_MANDPROF "missing_mandatory_profile"
+#define HINT_CHGPROF "changing_profile"
+
+#define LOG_HINT(p, gfp, hint, fmt, args...) \
+ do {\
+ aa_audit_message(p, gfp, 0, \
+ "LOGPROF-HINT " hint " " fmt, ##args);\
+ } while(0)
+
+/* directory op type, for aa_perm_dir */
+enum aa_diroptype {
+ aa_dir_mkdir,
+ aa_dir_rmdir
+};
+
+/* xattr op type, for aa_xattr */
+enum aa_xattroptype {
+ aa_xattr_get,
+ aa_xattr_set,
+ aa_xattr_list,
+ aa_xattr_remove
+};
+
+#define BASE_PROFILE(p) ((p)->parent ? (p)->parent : (p))
+#define IN_SUBPROFILE(p) ((p)->parent)
+
+/* main.c */
+extern int alloc_null_complain_profile(void);
+extern void free_null_complain_profile(void);
+extern int attach_nullprofile(struct aaprofile *profile);
+extern int aa_audit_message(struct aaprofile *active, unsigned int gfp, int,
+ const char *, ...);
+extern int aa_audit_syscallreject(struct aaprofile *active, unsigned int gfp,
+ const char *);
+extern int aa_audit(struct aaprofile *active, const struct aa_audit *);
+extern char *aa_get_name(struct dentry *dentry, struct vfsmount *mnt);
+
+extern int aa_attr(struct aaprofile *active, struct dentry *dentry,
+ struct iattr *iattr);
+extern int aa_xattr(struct aaprofile *active, struct dentry *dentry,
+ const char *xattr, enum aa_xattroptype xattroptype);
+extern int aa_capability(struct aaprofile *active, int cap);
+extern int aa_perm(struct aaprofile *active, struct dentry *dentry,
+ struct vfsmount *mnt, int mask);
+extern int aa_perm_nameidata(struct aaprofile *active, struct nameidata *nd,
+ int mask);
+extern int aa_perm_dentry(struct aaprofile *active, struct dentry *dentry,
+ int mask);
+extern int aa_perm_dir(struct aaprofile *active, struct dentry *dentry,
+ enum aa_diroptype diroptype);
+extern int aa_link(struct aaprofile *active,
+ struct dentry *link, struct dentry *target);
+extern int aa_fork(struct task_struct *p);
+extern int aa_register(struct file *file);
+extern void aa_release(struct task_struct *p);
+extern int aa_change_hat(const char *id, u32 hat_magic);
+extern int aa_associate_filp(struct file *filp);
+
+/* list.c */
+extern struct aaprofile *aa_profilelist_find(const char *name);
+extern int aa_profilelist_add(struct aaprofile *profile);
+extern struct aaprofile *aa_profilelist_remove(const char *name);
+extern void aa_profilelist_release(void);
+extern struct aaprofile *aa_profilelist_replace(struct aaprofile *profile);
+extern void aa_profile_dump(struct aaprofile *);
+extern void aa_profilelist_dump(void);
+extern void aa_subdomainlist_add(struct subdomain *);
+extern void aa_subdomainlist_remove(struct subdomain *);
+extern void aa_subdomainlist_iterate(aa_iter, void *);
+extern void aa_subdomainlist_iterateremove(aa_iter, void *);
+extern void aa_subdomainlist_release(void);
+
+/* module_interface.c */
+extern ssize_t aa_file_prof_add(void *, size_t);
+extern ssize_t aa_file_prof_repl(void *, size_t);
+extern ssize_t aa_file_prof_remove(const char *, size_t);
+extern void free_aaprofile(struct aaprofile *profile);
+extern void free_aaprofile_kref(struct kref *kref);
+
+/* procattr.c */
+extern size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size);
+extern int aa_setprocattr_changehat(char *hatinfo, size_t infosize);
+extern int aa_setprocattr_setprofile(struct task_struct *p, char *profilename,
+ size_t profilesize);
+
+/* apparmorfs.c */
+extern int create_apparmorfs(void);
+extern void destroy_apparmorfs(void);
+
+/* capabilities.c */
+extern const char *capability_to_name(unsigned int cap);
+
+#endif /* __SUBDOMAIN_H */
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/inline.h
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ */
+
+#ifndef __INLINE_H
+#define __INLINE_H
+
+#include <linux/namespace.h>
+
+static inline int __aa_is_confined(struct subdomain *sd)
+{
+ return (sd && sd->active);
+}
+
+/**
+ * aa_is_confined
+ * Determine whether current task contains a valid profile (confined).
+ * Return %1 if confined, %0 otherwise.
+ */
+static inline int aa_is_confined(void)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ return __aa_is_confined(sd);
+}
+
+static inline int __aa_sub_defined(struct subdomain *sd)
+{
+ return __aa_is_confined(sd) && !list_empty(&BASE_PROFILE(sd->active)->sub);
+}
+
+/**
+ * aa_sub_defined - check to see if current task has any subprofiles
+ * Return 1 if true, 0 otherwise
+ */
+static inline int aa_sub_defined(void)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(current->security);
+ return __aa_sub_defined(sd);
+}
+
+/**
+ * get_aaprofile - increment refcount on profile @p
+ * @p: profile
+ */
+static inline struct aaprofile *get_aaprofile(struct aaprofile *p)
+{
+ if (p)
+ kref_get(&(BASE_PROFILE(p)->count));
+
+ return p;
+}
+
+/**
+ * put_aaprofile - decrement refcount on profile @p
+ * @p: profile
+ */
+static inline void put_aaprofile(struct aaprofile *p)
+{
+ if (p)
+ kref_put(&BASE_PROFILE(p)->count, free_aaprofile_kref);
+}
+
+/**
+ * get_task_activeptr_rcu - get pointer to @tsk's active profile.
+ * @tsk: task to get active profile from
+ *
+ * Requires rcu_read_lock is held
+ */
+static inline struct aaprofile *get_task_activeptr_rcu(struct task_struct *tsk)
+{
+ struct subdomain *sd = AA_SUBDOMAIN(tsk->security);
+ struct aaprofile *active = NULL;
+
+ if (sd)
+ active = (struct aaprofile *) rcu_dereference(sd->active);
+
+ return active;
+}
+
+/**
+ * get_activeptr_rcu - get pointer to current task's active profile
+ * Requires rcu_read_lock is held
+ */
+static inline struct aaprofile *get_activeptr_rcu(void)
+{
+ return get_task_activeptr_rcu(current);
+}
+
+/**
+ * get_task_active_aaprofile - get a reference to tsk's active profile.
+ * @tsk: the task to get the active profile reference for
+ */
+static inline struct aaprofile *get_task_active_aaprofile(struct task_struct *tsk)
+{
+ struct aaprofile *active;
+
+ rcu_read_lock();
+ active = get_aaprofile(get_task_activeptr_rcu(tsk));
+ rcu_read_unlock();
+
+ return active;
+}
+
+/**
+ * get_active_aaprofile - get a reference to the current tasks active profile
+ */
+static inline struct aaprofile *get_active_aaprofile(void)
+{
+ return get_task_active_aaprofile(current);
+}
+
+/**
+ * aa_switch - change subdomain to use a new profile
+ * @sd: subdomain to switch the active profile on
+ * @newactive: new active profile
+ *
+ * aa_switch handles the changing of a subdomain's active profile. The
+ * sd_lock must be held to ensure consistency against other writers.
+ * Some write paths (ex. aa_register) require sd->active not to change
+ * over several operations, so the calling function is responsible
+ * for grabing the sd_lock to meet its consistency constraints before
+ * calling aa_switch
+ */
+static inline void aa_switch(struct subdomain *sd, struct aaprofile *newactive)
+{
+ struct aaprofile *oldactive = sd->active;
+
+ /* noop if NULL */
+ rcu_assign_pointer(sd->active, get_aaprofile(newactive));
+ put_aaprofile(oldactive);
+}
+
+/**
+ * aa_switch_unconfined - change subdomain to be unconfined (no profile)
+ * @sd: subdomain to switch
+ *
+ * aa_switch_unconfined handles the removal of a subdomain's active profile.
+ * The sd_lock must be held to ensure consistency against other writers.
+ * Like aa_switch the sd_lock is used to maintain consistency.
+ */
+static inline void aa_switch_unconfined(struct subdomain *sd)
+{
+ aa_switch(sd, NULL);
+
+ /* reset magic in case we were in a subhat before */
+ sd->hat_magic = 0;
+}
+
+/**
+ * alloc_subdomain - allocate a new subdomain
+ * @tsk: task struct
+ *
+ * Allocate a new subdomain including a backpointer to it's referring task.
+ */
+static inline struct subdomain *alloc_subdomain(struct task_struct *tsk)
+{
+ struct subdomain *sd;
+
+ sd = kzalloc(sizeof(struct subdomain), GFP_KERNEL);
+ if (!sd)
+ goto out;
+
+ /* back pointer to task */
+ sd->task = tsk;
+
+ /* any readers of the list must make sure that they can handle
+ * case where sd->active is not yet set (null)
+ */
+ aa_subdomainlist_add(sd);
+
+out:
+ return sd;
+}
+
+/**
+ * free_subdomain - Free a subdomain previously allocated by alloc_subdomain
+ * @sd: subdomain
+ */
+static inline void free_subdomain(struct subdomain *sd)
+{
+ aa_subdomainlist_remove(sd);
+ kfree(sd);
+}
+
+/**
+ * alloc_aaprofile - Allocate, initialize and return a new zeroed profile.
+ * Returns NULL on failure.
+ */
+static inline struct aaprofile *alloc_aaprofile(void)
+{
+ struct aaprofile *profile;
+
+ profile = (struct aaprofile *)kzalloc(sizeof(struct aaprofile),
+ GFP_KERNEL);
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
+ if (profile) {
+ int i;
+
+ INIT_LIST_HEAD(&profile->list);
+ INIT_LIST_HEAD(&profile->sub);
+ INIT_LIST_HEAD(&profile->file_entry);
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
+ INIT_LIST_HEAD(&profile->file_entryp[i]);
+ }
+ INIT_RCU_HEAD(&profile->rcu);
+ kref_init(&profile->count);
+ }
+ return profile;
+}
+
+/**
+ * aa_put_name
+ * @name: name to release.
+ *
+ * Release space (free_page) allocated to hold pathname
+ * name may be NULL (checked for by free_page)
+ */
+static inline void aa_put_name(const char *name)
+{
+ free_page((unsigned long)name);
+}
+
+/** __aa_find_profile
+ * @name: name of profile to find
+ * @head: list to search
+ *
+ * Return reference counted copy of profile. NULL if not found
+ * Caller must hold any necessary locks
+ */
+static inline struct aaprofile *__aa_find_profile(const char *name,
+ struct list_head *head)
+{
+ struct aaprofile *p;
+
+ if (!name || !head)
+ return NULL;
+
+ AA_DEBUG("%s: finding profile %s\n", __FUNCTION__, name);
+ list_for_each_entry(p, head, list) {
+ if (!strcmp(p->name, name)) {
+ /* return refcounted object */
+ p = get_aaprofile(p);
+ return p;
+ } else {
+ AA_DEBUG("%s: skipping %s\n", __FUNCTION__, p->name);
+ }
+ }
+ return NULL;
+}
+
+/** __aa_path_begin
+ * @rdentry: filesystem root dentry (searching for vfsmnts matching this)
+ * @dentry: dentry object to obtain pathname from (relative to matched vfsmnt)
+ *
+ * Setup data for iterating over vfsmounts (in current tasks namespace).
+ */
+static inline void __aa_path_begin(struct dentry *rdentry,
+ struct dentry *dentry,
+ struct aa_path_data *data)
+{
+ data->dentry = dentry;
+ data->root = dget(rdentry->d_sb->s_root);
+ data->namespace = current->namespace;
+ data->head = &data->namespace->list;
+ data->pos = data->head->next;
+ prefetch(data->pos->next);
+ data->errno = 0;
+
+ down_read(&namespace_sem);
+}
+
+/** aa_path_begin
+ * @dentry: filesystem root dentry and object to obtain pathname from
+ *
+ * Utility function for calling _aa_path_begin for when the dentry we are
+ * looking for and the root are the same (this is the usual case).
+ */
+static inline void aa_path_begin(struct dentry *dentry,
+ struct aa_path_data *data)
+{
+ __aa_path_begin(dentry, dentry, data);
+}
+
+/** aa_path_end
+ * @data: data object previously initialized by aa_path_begin
+ *
+ * End iterating over vfsmounts.
+ * If an error occured in begin or get, it is returned. Otherwise 0.
+ */
+static inline int aa_path_end(struct aa_path_data *data)
+{
+ up_read(&namespace_sem);
+ dput(data->root);
+
+ return data->errno;
+}
+
+/** aa_path_getname
+ * @data: data object previously initialized by aa_path_begin
+ *
+ * Return the next mountpoint which has the same root dentry as data->root.
+ * If no more mount points exist (or in case of error) NULL is returned
+ * (caller should call aa_path_end() and inspect return code to differentiate)
+ */
+static inline char *aa_path_getname(struct aa_path_data *data)
+{
+ char *name = NULL;
+ struct vfsmount *mnt;
+
+ while (data->pos != data->head) {
+ mnt = list_entry(data->pos, struct vfsmount, mnt_list);
+
+ /* advance to next -- so that it is done before we break */
+ data->pos = data->pos->next;
+ prefetch(data->pos->next);
+
+ if (mnt->mnt_root == data->root) {
+ name = aa_get_name(data->dentry, mnt);
+ if (!name)
+ data->errno = -ENOMEM;
+ break;
+ }
+ }
+
+ return name;
+}
+
+#endif /* __INLINE_H__ */
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/shared.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2000, 2001, 2004, 2005 Novell/SUSE
+ *
+ * Immunix AppArmor LSM
+ *
+ * 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 _SHARED_H
+#define _SHARED_H
+
+/* start of system offsets */
+#define POS_AA_FILE_MIN 0
+#define POS_AA_MAY_EXEC POS_AA_FILE_MIN
+#define POS_AA_MAY_WRITE (POS_AA_MAY_EXEC + 1)
+#define POS_AA_MAY_READ (POS_AA_MAY_WRITE + 1)
+#define POS_AA_MAY_APPEND (POS_AA_MAY_READ + 1)
+/* end of system offsets */
+
+#define POS_AA_MAY_LINK (POS_AA_MAY_APPEND + 1)
+#define POS_AA_EXEC_INHERIT (POS_AA_MAY_LINK + 1)
+#define POS_AA_EXEC_UNCONSTRAINED (POS_AA_EXEC_INHERIT + 1)
+#define POS_AA_EXEC_PROFILE (POS_AA_EXEC_UNCONSTRAINED + 1)
+#define POS_AA_FILE_MAX POS_AA_EXEC_PROFILE
+
+/* Modeled after MAY_READ, MAY_WRITE, MAY_EXEC def'ns */
+#define AA_MAY_EXEC (0x01 << POS_AA_MAY_EXEC)
+#define AA_MAY_WRITE (0x01 << POS_AA_MAY_WRITE)
+#define AA_MAY_READ (0x01 << POS_AA_MAY_READ)
+#define AA_MAY_LINK (0x01 << POS_AA_MAY_LINK)
+#define AA_EXEC_INHERIT (0x01 << POS_AA_EXEC_INHERIT)
+#define AA_EXEC_UNCONSTRAINED (0x01 << POS_AA_EXEC_UNCONSTRAINED)
+#define AA_EXEC_PROFILE (0x01 << POS_AA_EXEC_PROFILE)
+#define AA_EXEC_MODIFIERS(X) (X & (AA_EXEC_INHERIT | \
+ A_EXEC_UNCONSTRAINED | \
+ AA_EXEC_PROFILE))
+
+#endif /* _SHARED_H */
Arjan van de Ven
2006-04-19 18:01:30 UTC
Permalink
Post by Tony Jones
This patch provides the various common headerfiles used by the AppArmor module.
apparmor.h contains the core data structures.
shared.h contains definitions that are common to the userspace policy loader.
inline.h implements various inline utility functions
---
security/apparmor/apparmor.h | 325 +++++++++++++++++++++++++++++++++++++++++
security/apparmor/inline.h | 333 +++++++++++++++++++++++++++++++++++++++++++
security/apparmor/shared.h | 41 +++++
3 files changed, 699 insertions(+)
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/apparmor.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 1998-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor internal prototypes
+ */
+
+#ifndef __SUBDOMAIN_H
+#define __SUBDOMAIN_H
this is an odd include guard for a file called apparmor.h
Post by Tony Jones
+#include "shared.h"
+
+/* Control parameters (0 or 1), settable thru module/boot flags or
+ * via /sys/kernel/security/apparmor/control */
+extern int apparmor_complain;
+extern int apparmor_debug;
+extern int apparmor_audit;
+extern int apparmor_logsyscall;
looks like these should be in a header too
Post by Tony Jones
+
+/* PIPEFS_MAGIC */
+#include <linux/pipe_fs_i.h>
+/* from net/socket.c */
+#define SOCKFS_MAGIC 0x534F434B
+/* from inotify.c */
+#define INOTIFYFS_MAGIC 0xBAD1DEA
+
+#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \
+ (inode)->i_sb->s_magic != SOCKFS_MAGIC && \
+ (inode)->i_sb->s_magic != INOTIFYFS_MAGIC)
ehhhh what is this about? Isn't this highly fragile???
Post by Tony Jones
+
+/*
+ * 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 (apparmor_debug) \
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
+ } while (0)
+#define AA_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args)
+#define AA_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args)
+#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
+
eh why? at least use prdebug and the like, but don't do your own
Post by Tony Jones
+/* 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.
why duplicate the audit infrastructure??

(and it's not possible to use LSM for auditing really; so it's not just
duplication, it's a bad idea)
Post by Tony Jones
+/** aa_path_getname
+ *
+ * Return the next mountpoint which has the same root dentry as data->root.
+ * If no more mount points exist (or in case of error) NULL is returned
+ * (caller should call aa_path_end() and inspect return code to differentiate)
+ */
+static inline char *aa_path_getname(struct aa_path_data *data)
+{
+ char *name = NULL;
+ struct vfsmount *mnt;
+
+ while (data->pos != data->head) {
+ mnt = list_entry(data->pos, struct vfsmount, mnt_list);
+
+ /* advance to next -- so that it is done before we break */
+ data->pos = data->pos->next;
+ prefetch(data->pos->next);
+
+ if (mnt->mnt_root == data->root) {
+ name = aa_get_name(data->dentry, mnt);
+ if (!name)
+ data->errno = -ENOMEM;
+ break;
+ }
+ }
+
+ return name;
what's the locking rules and refcounting rules for this stuff ??



-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 17:43:34 UTC
Permalink
Post by Arjan van de Ven
Post by Tony Jones
+#ifndef __SUBDOMAIN_H
+#define __SUBDOMAIN_H
this is an odd include guard for a file called apparmor.h
yes, noticed it 10 secs after I posted. Fixed.
Post by Arjan van de Ven
Post by Tony Jones
+#include "shared.h"
+
+/* Control parameters (0 or 1), settable thru module/boot flags or
+ * via /sys/kernel/security/apparmor/control */
+extern int apparmor_complain;
+extern int apparmor_debug;
+extern int apparmor_audit;
+extern int apparmor_logsyscall;
looks like these should be in a header too
Ok.
Post by Arjan van de Ven
Post by Tony Jones
+
+/* PIPEFS_MAGIC */
+#include <linux/pipe_fs_i.h>
+/* from net/socket.c */
+#define SOCKFS_MAGIC 0x534F434B
+/* from inotify.c */
+#define INOTIFYFS_MAGIC 0xBAD1DEA
+
+#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \
+ (inode)->i_sb->s_magic != SOCKFS_MAGIC && \
+ (inode)->i_sb->s_magic != INOTIFYFS_MAGIC)
ehhhh what is this about? Isn't this highly fragile???
Clearly a better comment is necessary.
There are a set of filesystem types for which we don't believe pathbased
mediation is relevant. Obviously for a system which is inode based mediating
these filesystems makes more sense. I'm unsure if it is fragile. Clearly
the set could grow, if this is what you mean, then yes, a less "fragile" way
of determining these would be useful.
Post by Arjan van de Ven
Post by Tony Jones
+ if (apparmor_debug) \
+ printk(KERN_DEBUG "AppArmor: " fmt, ##args); \
+ } while (0)
+#define AA_INFO(fmt, args...) printk(KERN_INFO "AppArmor: " fmt, ##args)
+#define AA_WARN(fmt, args...) printk(KERN_WARNING "AppArmor: " fmt, ##args)
+#define AA_ERROR(fmt, args...) printk(KERN_ERR "AppArmor: " fmt, ##args)
+
eh why? at least use prdebug and the like, but don't do your own
Ok. Thanks. I'll look at it.
Post by Arjan van de Ven
Post by Tony Jones
+/* 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.
why duplicate the audit infrastructure??
(and it's not possible to use LSM for auditing really; so it's not just
duplication, it's a bad idea)
We're not duplicating. aa_audit is just a common datastructure where AA
event information is stored. It is passed to aa_audit which logs it to
the kernel audit subsystem. It facilitates a single point of logging.

If you think this can be improved (clearly another case for better commenting)
I'd be curious to hear your views.
Post by Arjan van de Ven
Post by Tony Jones
+/** aa_path_getname
+ *
+ * Return the next mountpoint which has the same root dentry as data->root.
+ * If no more mount points exist (or in case of error) NULL is returned
+ * (caller should call aa_path_end() and inspect return code to differentiate)
+ */
+static inline char *aa_path_getname(struct aa_path_data *data)
+{
+ char *name = NULL;
+ struct vfsmount *mnt;
+
+ while (data->pos != data->head) {
+ mnt = list_entry(data->pos, struct vfsmount, mnt_list);
+
+ /* advance to next -- so that it is done before we break */
+ data->pos = data->pos->next;
+ prefetch(data->pos->next);
+
+ if (mnt->mnt_root == data->root) {
+ name = aa_get_name(data->dentry, mnt);
+ if (!name)
+ data->errno = -ENOMEM;
+ break;
+ }
+ }
+
+ return name;
what's the locking rules and refcounting rules for this stuff ??
This is where the issue of the namespace_sem rears it's ugly head.
The issue isn't the namespace sem, it's the lack of vfsmount information
passed to LSM from the VFS, It facilitates the need for this function.
The change in visibility of the per namespace semaphore in the shared
subtree changes just makes it a little bit worse.

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:50:26 UTC
Permalink
This patch adds a new function d_path_flags which takes an additional flags
parameter. Adding a new function rather than ammending the existing d_path
was done to avoid impact on the current users.

It is not essential for inclusion with AppArmor (the apparmor_mediation.patch
can easily be revised to use plain d_path) but it enables cleaner code
["(delete)" handling] and closes a loophole with pathname generation for
chrooted tasks.

It currently adds two flags:

DPATH_SYSROOT:
d_path should generate a path from the system root rather than the
task's current root.

For AppArmor this enables generation of absolute pathnames in all
cases. Currently when a task is chrooted, file access is reported
relative to the chroot. Because it is currently not possible to
obtain the absolute path in an SMP safe way, without this patch
AppArmor will have to report chroot-relative pathnames.

DPATH_NODELETED:
d_path should not append "(deleted)" to unhashed entries. Sometimes
this information is not useful for the caller and the string can
exist as the suffix of a valid pathname.

Signed-off-by: Tony Jones <***@suse.de>

---
fs/dcache.c | 48 ++++++++++++++++++++++++++++++++----------------
include/linux/dcache.h | 7 +++++++
2 files changed, 39 insertions(+), 16 deletions(-)

--- linux-2.6.17-rc1.orig/fs/dcache.c
+++ linux-2.6.17-rc1/fs/dcache.c
@@ -1381,9 +1381,11 @@
* @rootmnt: vfsmnt to which the root dentry belongs
* @buffer: buffer to return value in
* @buflen: buffer length
+ * @flags: control flags
*
* Convert a dentry into an ASCII path name. If the entry has been deleted
- * the string " (deleted)" is appended. Note that this is ambiguous.
+ * and DPATH_NODELETED is not specified in flags then the string " (deleted)"
+ * is appended. Note that this is ambiguous.
*
* Returns the buffer or an error code if the path was too long.
*
@@ -1391,7 +1393,7 @@
*/
static char * __d_path( struct dentry *dentry, struct vfsmount *vfsmnt,
struct dentry *root, struct vfsmount *rootmnt,
- char *buffer, int buflen)
+ char *buffer, int buflen, unsigned int flags)
{
char * end = buffer+buflen;
char * retval;
@@ -1399,7 +1401,8 @@

*--end = '\0';
buflen--;
- if (!IS_ROOT(dentry) && d_unhashed(dentry)) {
+ if (!(flags & DPATH_NODELETED) &&
+ !IS_ROOT(dentry) && d_unhashed(dentry)) {
buflen -= 10;
end -= 10;
if (buflen < 0)
@@ -1416,7 +1419,8 @@
for (;;) {
struct dentry * parent;

- if (dentry == root && vfsmnt == rootmnt)
+ if (!(flags & DPATH_SYSROOT) &&
+ dentry == root && vfsmnt == rootmnt)
break;
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
/* Global root? */
@@ -1458,25 +1462,36 @@
}

/* write full pathname into buffer and return start of pathname */
-char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
- char *buf, int buflen)
+char * d_path_flags(struct dentry *dentry, struct vfsmount *vfsmnt,
+ char *buf, int buflen, unsigned int flags)
{
char *res;
- struct vfsmount *rootmnt;
- struct dentry *root;
+ struct vfsmount *rootmnt = NULL;
+ struct dentry *root = NULL;

- read_lock(&current->fs->lock);
- rootmnt = mntget(current->fs->rootmnt);
- root = dget(current->fs->root);
- read_unlock(&current->fs->lock);
+ if (!(flags & DPATH_SYSROOT)){
+ read_lock(&current->fs->lock);
+ rootmnt = mntget(current->fs->rootmnt);
+ root = dget(current->fs->root);
+ read_unlock(&current->fs->lock);
+ }
spin_lock(&dcache_lock);
- res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen);
+ res = __d_path(dentry, vfsmnt, root, rootmnt, buf, buflen, flags);
spin_unlock(&dcache_lock);
- dput(root);
- mntput(rootmnt);
+ if (!(flags & DPATH_SYSROOT)){
+ dput(root);
+ mntput(rootmnt);
+ }
return res;
}

+/* original d_path without support for flags */
+char * d_path(struct dentry *dentry, struct vfsmount *vfsmnt,
+ char *buf, int buflen)
+{
+ return d_path_flags(dentry, vfsmnt, buf, buflen, 0);
+}
+
/*
* NOTE! The user-level library version returns a
* character pointer. The kernel system call just
@@ -1519,7 +1534,7 @@
unsigned long len;
char * cwd;

- cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE);
+ cwd = __d_path(pwd, pwdmnt, root, rootmnt, page, PAGE_SIZE, 0);
spin_unlock(&dcache_lock);

error = PTR_ERR(cwd);
@@ -1771,6 +1786,7 @@
EXPORT_SYMBOL(d_invalidate);
EXPORT_SYMBOL(d_lookup);
EXPORT_SYMBOL(d_move);
+EXPORT_SYMBOL(d_path_flags);
EXPORT_SYMBOL(d_path);
EXPORT_SYMBOL(d_prune_aliases);
EXPORT_SYMBOL(d_rehash);
--- linux-2.6.17-rc1.orig/include/linux/dcache.h
+++ linux-2.6.17-rc1/include/linux/dcache.h
@@ -164,6 +164,10 @@

#define DCACHE_INOTIFY_PARENT_WATCHED 0x0020 /* Parent inode is watched */

+/* dpath flags */
+#define DPATH_SYSROOT 0x0001 /* continue past fsroot (chroot) */
+#define DPATH_NODELETED 0x0002 /* do not append " (deleted)" */
+
extern spinlock_t dcache_lock;

/**
@@ -281,6 +285,9 @@
extern int d_validate(struct dentry *, struct dentry *);

extern char * d_path(struct dentry *, struct vfsmount *, char *, int);
+
+extern char * d_path_flags(struct dentry *, struct vfsmount *, char *, int,
+ unsigned int);

/* Allocation counts.. */
Christoph Hellwig
2006-04-19 22:12:48 UTC
Permalink
Post by Tony Jones
This patch adds a new function d_path_flags which takes an additional flags
parameter. Adding a new function rather than ammending the existing d_path
was done to avoid impact on the current users.
It is not essential for inclusion with AppArmor (the apparmor_mediation.patch
can easily be revised to use plain d_path) but it enables cleaner code
["(delete)" handling] and closes a loophole with pathname generation for
chrooted tasks.
d_path should generate a path from the system root rather than the
task's current root.
For AppArmor this enables generation of absolute pathnames in all
cases. Currently when a task is chrooted, file access is reported
relative to the chroot. Because it is currently not possible to
obtain the absolute path in an SMP safe way, without this patch
AppArmor will have to report chroot-relative pathnames.
This is utter bullshit. There is no such thing as a system root,
and should not rely on pathes making any sense for anything but the
process using at at this point of time. This stuff will not get in either
in d_path or whatever duplicate of it you'd try to submit.

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 05:36:04 UTC
Permalink
Post by Christoph Hellwig
Post by Tony Jones
This patch adds a new function d_path_flags which takes an additional flags
parameter. Adding a new function rather than ammending the existing d_path
was done to avoid impact on the current users.
It is not essential for inclusion with AppArmor (the apparmor_mediation.patch
can easily be revised to use plain d_path) but it enables cleaner code
["(delete)" handling] and closes a loophole with pathname generation for
chrooted tasks.
d_path should generate a path from the system root rather than the
task's current root.
For AppArmor this enables generation of absolute pathnames in all
cases. Currently when a task is chrooted, file access is reported
relative to the chroot. Because it is currently not possible to
obtain the absolute path in an SMP safe way, without this patch
AppArmor will have to report chroot-relative pathnames.
This is utter bullshit. There is no such thing as a system root,
and should not rely on pathes making any sense for anything but the
process using at at this point of time. This stuff will not get in either
in d_path or whatever duplicate of it you'd try to submit.
You are correct on calling BS in that I was wrong to refer to it as the
"system root". When a task chroots relative to it's current namespace, we
are interested in the path back to the root of that namespace, rather than
to the chroot. I believe the patch as stands achieves this, albeit with
some changing of comments.

thanks!

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Arjan van de Ven
2006-04-20 08:26:09 UTC
Permalink
Post by Tony Jones
You are correct on calling BS in that I was wrong to refer to it as the
"system root". When a task chroots relative to it's current namespace, we
are interested in the path back to the root of that namespace, rather than
to the chroot. I believe the patch as stands achieves this, albeit with
some changing of comments.
it actually doesn't; you assume there is such a path which is not a
given. For example if your mount got lazy umounted (like hal probably
does) then it's a floating mount not one tied to any tree going to the
root of any namespace.


-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 16:43:29 UTC
Permalink
Post by Arjan van de Ven
Post by Tony Jones
You are correct on calling BS in that I was wrong to refer to it as the
"system root". When a task chroots relative to it's current namespace, we
are interested in the path back to the root of that namespace, rather than
to the chroot. I believe the patch as stands achieves this, albeit with
some changing of comments.
it actually doesn't; you assume there is such a path which is not a
given. For example if your mount got lazy umounted (like hal probably
does) then it's a floating mount not one tied to any tree going to the
root of any namespace.
So, running with your "lazy unmounted" example for a bit.

The patch I proposed changes how d_path behaves when the task has chrooted
relative to it's namespace. So in your scenario what would calling d_path
on a dentry report (for !chrooted and chrooted) without this patch ?

I can't tell if you are claiming there is a fundamental problem calling d_path
*period* in this scenario. If so, I'd appreciate a little more concrete detail
in the way of an actual example, this is a bit hand-wavy.

Or that you are just saying another version of "pathames are crap" which I'm
not sure if appropos to this patch itself.

If it's the former, I'll happily go off and write some code to test your
assertion and it's ramifications if I can better understand what the actual
assertion is :-)

Thanks

Tony
Christoph Hellwig
2006-04-20 17:04:19 UTC
Permalink
Post by Tony Jones
I can't tell if you are claiming there is a fundamental problem calling d_path
*period* in this scenario. If so, I'd appreciate a little more concrete detail
The purpose of d_path is to give user information about a path, to be
used in things like procfs output. For everything else it's fundamentally
broken and shouldn't be used. And for exactly that reason it isn't used for
anything like that in the whole tree (except the possible fishy use in nfsd).

p.s.: I also see that your patch doesn't include on to export d_path so
couldn't actually use it anyway. Not that a patch to export it would ever
be ACKed for above reasons..
Post by Tony Jones
in the way of an actual example, this is a bit hand-wavy.
Or that you are just saying another version of "pathames are crap" which I'm
not sure if appropos to this patch itself.
If it's the former, I'll happily go off and write some code to test your
assertion and it's ramifications if I can better understand what the actual
assertion is :-)
Thanks
Tony
---end quoted text---
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 17:50:43 UTC
Permalink
Post by Christoph Hellwig
p.s.: I also see that your patch doesn't include on to export d_path so
couldn't actually use it anyway. Not that a patch to export it would ever
be ACKed for above reasons..
Don't understand. Are you saying there is no EXPORT_SYMBOL for d_path?

I didn't add one as I didn't remove the old one. It's still there.

Thanks

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 12:16:19 UTC
Permalink
Post by Tony Jones
Post by Christoph Hellwig
p.s.: I also see that your patch doesn't include on to export d_path so
couldn't actually use it anyway. Not that a patch to export it would ever
be ACKed for above reasons..
Don't understand. Are you saying there is no EXPORT_SYMBOL for d_path?
I didn't add one as I didn't remove the old one. It's still there.
Yes, it does appear to be exported already, presumably for nfsd. Still
leaves open the question of whether it should be exported, or more
importantly what is considered legitimate use of it.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:50:02 UTC
Permalink
This patch implements three distinct chunks.
- list management, for profiles loaded into the system (profile_list) and for
the set of confined tasks (subdomain_list)
- the proc/pid/attr interface used by userspace for setprofile (forcing
a task into a new profile) and changehat (switching a task into one of it's
defined sub profiles). Access to change_hat is normally via code provided
in libapparmor. See the overview posting for more information in change hat.
- capability utility functions (for displaying capability names)


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/capabilities.c | 54 ++++++
security/apparmor/list.c | 268 +++++++++++++++++++++++++++++++
security/apparmor/procattr.c | 327 +++++++++++++++++++++++++++++++++++++++
3 files changed, 649 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/capabilities.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor capability definitions
+ */
+
+#include "apparmor.h"
+
+static const char *cap_names[] = {
+ "chown",
+ "dac_override",
+ "dac_read_search",
+ "fowner",
+ "fsetid",
+ "kill",
+ "setgid",
+ "setuid",
+ "setpcap",
+ "linux_immutable",
+ "net_bind_service",
+ "net_broadcast",
+ "net_admin",
+ "net_raw",
+ "ipc_lock",
+ "ipc_owner",
+ "sys_module",
+ "sys_rawio",
+ "sys_chroot",
+ "sys_ptrace",
+ "sys_pacct",
+ "sys_admin",
+ "sys_boot",
+ "sys_nice",
+ "sys_resource",
+ "sys_time",
+ "sys_tty_config",
+ "mknod",
+ "lease"
+};
+
+const char *capability_to_name(unsigned int cap)
+{
+ const char *name;
+
+ name = (cap < (sizeof(cap_names) / sizeof(char *))
+ ? cap_names[cap] : "invalid-capability");
+
+ return name;
+}
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/list.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 1998-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor Profile List Management
+ */
+
+#include <linux/seq_file.h>
+#include "apparmor.h"
+#include "inline.h"
+
+/* list of all profiles and lock */
+static LIST_HEAD(profile_list);
+static rwlock_t profile_lock = RW_LOCK_UNLOCKED;
+
+/* list of all subdomains and lock */
+static LIST_HEAD(subdomain_list);
+static rwlock_t subdomain_lock = RW_LOCK_UNLOCKED;
+
+/**
+ * aa_profilelist_find
+ * @name: profile name (program name)
+ *
+ * Search the profile list for profile @name. Return refcounted profile on
+ * success, NULL on failure.
+ */
+struct aaprofile *aa_profilelist_find(const char *name)
+{
+ struct aaprofile *p = NULL;
+ if (name) {
+ read_lock(&profile_lock);
+ p = __aa_find_profile(name, &profile_list);
+ read_unlock(&profile_lock);
+ }
+ return p;
+}
+
+/**
+ * aa_profilelist_add - add new profile to list
+ * @profile: new profile to add to list
+ *
+ * NOTE: Caller must allocate necessary reference count that will be used
+ * by the profile_list. This is because profile allocation alloc_aaprofile()
+ * returns an unreferenced object with a initial count of %1.
+ *
+ * Return %1 on success, %0 on failure (already exists)
+ */
+int aa_profilelist_add(struct aaprofile *profile)
+{
+ struct aaprofile *old_profile;
+ int ret = 0;
+
+ if (!profile)
+ goto out;
+
+ write_lock(&profile_lock);
+ old_profile = __aa_find_profile(profile->name, &profile_list);
+ if (old_profile) {
+ put_aaprofile(old_profile);
+ goto out;
+ }
+
+ list_add(&profile->list, &profile_list);
+ ret = 1;
+ out:
+ write_unlock(&profile_lock);
+ return ret;
+}
+
+/**
+ * aa_profilelist_remove - remove a profile from the list by name
+ * @name: name of profile to be removed
+ *
+ * If the profile exists remove profile from list and return its reference.
+ * The reference count on profile is not decremented and should be decremented
+ * when the profile is no longer needed
+ */
+struct aaprofile *aa_profilelist_remove(const char *name)
+{
+ struct aaprofile *profile = NULL;
+ struct aaprofile *p, *tmp;
+
+ if (!name)
+ goto out;
+
+ write_lock(&profile_lock);
+ list_for_each_entry_safe(p, tmp, &profile_list, list) {
+ if (!strcmp(p->name, name)) {
+ list_del_init(&p->list);
+ /* mark old profile as stale */
+ p->isstale = 1;
+ profile = p;
+ break;
+ }
+ }
+ write_unlock(&profile_lock);
+
+out:
+ return profile;
+}
+
+/**
+ * aa_profilelist_replace - replace a profile on the list
+ * @profile: new profile
+ *
+ * Replace a profile on the profile list. Find the old profile by name in
+ * the list, and replace it with the new profile. NOTE: Caller must allocate
+ * necessary initial reference count for new profile as aa_profilelist_add().
+ *
+ * This is an atomic list operation. Returns the old profile (which is still
+ * refcounted) if there was one, or NULL.
+ */
+struct aaprofile *aa_profilelist_replace(struct aaprofile *profile)
+{
+ struct aaprofile *oldprofile;
+
+ write_lock(&profile_lock);
+ oldprofile = __aa_find_profile(profile->name, &profile_list);
+ if (oldprofile) {
+ list_del_init(&oldprofile->list);
+ /* mark old profile as stale */
+ oldprofile->isstale = 1;
+
+ /* __aa_find_profile incremented count, so adjust down */
+ put_aaprofile(oldprofile);
+ }
+
+ list_add(&profile->list, &profile_list);
+ write_unlock(&profile_lock);
+
+ return oldprofile;
+}
+
+/**
+ * aa_profilelist_release - Remove all profiles from profile_list
+ */
+void aa_profilelist_release(void)
+{
+ struct aaprofile *p, *tmp;
+
+ write_lock(&profile_lock);
+ list_for_each_entry_safe(p, tmp, &profile_list, list) {
+ list_del_init(&p->list);
+ put_aaprofile(p);
+ }
+ write_unlock(&profile_lock);
+}
+
+/**
+ * aa_subdomainlist_add - Add subdomain to subdomain_list
+ * @sd: new subdomain
+ */
+void aa_subdomainlist_add(struct subdomain *sd)
+{
+ unsigned long flags;
+
+ if (!sd) {
+ AA_INFO("%s: bad subdomain\n", __FUNCTION__);
+ return;
+ }
+
+ write_lock_irqsave(&subdomain_lock, flags);
+ /* new subdomains must be added to the end of the list due to a
+ * subtle interaction between fork and profile replacement.
+ */
+ list_add_tail(&sd->list, &subdomain_list);
+ write_unlock_irqrestore(&subdomain_lock, flags);
+}
+
+/**
+ * aa_subdomainlist_remove - Remove subdomain from subdomain_list
+ * @sd: subdomain to be removed
+ */
+void aa_subdomainlist_remove(struct subdomain *sd)
+{
+ unsigned long flags;
+
+ if (sd) {
+ write_lock_irqsave(&subdomain_lock, flags);
+ list_del_init(&sd->list);
+ write_unlock_irqrestore(&subdomain_lock, flags);
+ }
+}
+
+/**
+ * aa_subdomainlist_iterate - iterate over the subdomain list applying @func
+ * @func: method to be called for each element
+ * @cookie: user passed data
+ *
+ * Iterate over subdomain list applying @func, stop when @func returns
+ * non zero
+ */
+void aa_subdomainlist_iterate(aa_iter func, void *cookie)
+{
+ struct subdomain *node;
+ int ret = 0;
+ unsigned long flags;
+
+ read_lock_irqsave(&subdomain_lock, flags);
+ list_for_each_entry(node, &subdomain_list, list) {
+ ret = (*func) (node, cookie);
+ if (ret != 0)
+ break;
+ }
+ read_unlock_irqrestore(&subdomain_lock, flags);
+}
+
+/**
+ * aa_subdomainlist_release - Remove all subdomains from subdomain_list
+ */
+void aa_subdomainlist_release()
+{
+ struct subdomain *node, *tmp;
+ unsigned long flags;
+
+ write_lock_irqsave(&subdomain_lock, flags);
+ list_for_each_entry_safe(node, tmp, &subdomain_list, list) {
+ list_del_init(&node->list);
+ }
+ write_unlock_irqrestore(&subdomain_lock, flags);
+}
+
+/* seq_file helper routines
+ * Used by apparmorfs.c to iterate over profile_list
+ */
+static void *p_start(struct seq_file *f, loff_t *pos)
+{
+ struct aaprofile *node;
+ loff_t l = *pos;
+
+ read_lock(&profile_lock);
+ list_for_each_entry(node, &profile_list, list)
+ if (!l--)
+ return node;
+ return NULL;
+}
+
+static void *p_next(struct seq_file *f, void *p, loff_t *pos)
+{
+ struct list_head *lh = ((struct aaprofile *)p)->list.next;
+ (*pos)++;
+ return lh == &profile_list ?
+ NULL : list_entry(lh, struct aaprofile, list);
+}
+
+static void p_stop(struct seq_file *f, void *v)
+{
+ read_unlock(&profile_lock);
+}
+
+static int seq_show_profile(struct seq_file *f, void *v)
+{
+ struct aaprofile *profile = (struct aaprofile *)v;
+ seq_printf(f, "%s (%s)\n", profile->name,
+ PROFILE_COMPLAIN(profile) ? "complain" : "enforce");
+ return 0;
+}
+
+struct seq_operations apparmorfs_profiles_op = {
+ .start = p_start,
+ .next = p_next,
+ .stop = p_stop,
+ .show = seq_show_profile,
+};
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/procattr.c
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor /proc/pid/attr handling
+ */
+
+/* for isspace */
+#include <linux/ctype.h>
+
+#include "apparmor.h"
+#include "inline.h"
+
+size_t aa_getprocattr(struct aaprofile *active, char *str, size_t size)
+{
+ int error = -EACCES; /* default to a perm denied */
+ size_t len;
+
+ if (active) {
+ size_t lena, lenm, lenp = 0;
+ const char *enforce_str = " (enforce)";
+ const char *complain_str = " (complain)";
+ const char *mode_str =
+ PROFILE_COMPLAIN(active) ? complain_str : enforce_str;
+
+ lenm = strlen(mode_str);
+
+ lena = strlen(active->name);
+
+ len = lena;
+ if (IN_SUBPROFILE(active)) {
+ lenp = strlen(BASE_PROFILE(active)->name);
+ len += (lenp + 1); /* +1 for ^ */
+ }
+ /* DONT null terminate strings we output via proc */
+ len += (lenm + 1); /* for \n */
+
+ if (len <= size) {
+ if (lenp) {
+ memcpy(str, BASE_PROFILE(active)->name,
+ lenp);
+ str += lenp;
+ *str++ = '^';
+ }
+
+ memcpy(str, active->name, lena);
+ str += lena;
+ memcpy(str, mode_str, lenm);
+ str += lenm;
+ *str++ = '\n';
+ error = len;
+ } else {
+ error = -ERANGE;
+ }
+ } else {
+ const char *unconstrained_str = "unconstrained\n";
+ len = strlen(unconstrained_str);
+
+ /* DONT null terminate strings we output via proc */
+ if (len <= size) {
+ memcpy(str, unconstrained_str, len);
+ error = len;
+ } else {
+ error = -ERANGE;
+ }
+ }
+
+ return error;
+
+}
+
+int aa_setprocattr_changehat(char *hatinfo, size_t infosize)
+{
+ int error = -EINVAL;
+ char *token = NULL, *hat, *smagic, *tmp;
+ u32 magic;
+ int rc, len, consumed;
+ unsigned long flags;
+
+ AA_DEBUG("%s: %p %zd\n", __FUNCTION__, hatinfo, infosize);
+
+ /* strip leading white space */
+ while (infosize && isspace(*hatinfo)) {
+ hatinfo++;
+ infosize--;
+ }
+
+ if (infosize == 0)
+ goto out;
+
+ /*
+ * Copy string to a new buffer so we can play with it
+ * It may be zero terminated but we add a trailing 0
+ * for 100% safety
+ */
+ token = kmalloc(infosize + 1, GFP_KERNEL);
+
+ if (!token) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(token, hatinfo, infosize);
+ token[infosize] = 0;
+
+ /* error is INVAL until we have at least parsed something */
+ error = -EINVAL;
+
+ tmp = token;
+ while (*tmp && *tmp != '^') {
+ tmp++;
+ }
+
+ if (!*tmp || tmp == token) {
+ AA_WARN("%s: Invalid input '%s'\n", __FUNCTION__, token);
+ goto out;
+ }
+
+ /* split magic and hat into two strings */
+ *tmp = 0;
+ smagic = token;
+
+ /*
+ * Initially set consumed=strlen(magic), as if sscanf
+ * consumes all input via the %x it will not process the %n
+ * directive. Otherwise, if sscanf does not consume all the
+ * input it will process the %n and update consumed.
+ */
+ consumed = len = strlen(smagic);
+
+ rc = sscanf(smagic, "%x%n", &magic, &consumed);
+
+ if (rc != 1 || consumed != len) {
+ AA_WARN("%s: Invalid hex magic %s\n",
+ __FUNCTION__,
+ smagic);
+ goto out;
+ }
+
+ hat = tmp + 1;
+
+ if (!*hat)
+ hat = NULL;
+
+ if (!hat && !magic) {
+ AA_WARN("%s: Invalid input, NULL hat and NULL magic\n",
+ __FUNCTION__);
+ goto out;
+ }
+
+ AA_DEBUG("%s: Magic 0x%x Hat '%s'\n",
+ __FUNCTION__, magic, hat ? hat : NULL);
+
+ spin_lock_irqsave(&sd_lock, flags);
+ error = aa_change_hat(hat, magic);
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+out:
+ if (token) {
+ memset(token, 0, infosize);
+ kfree(token);
+ }
+
+ return error;
+}
+
+int aa_setprocattr_setprofile(struct task_struct *p, char *profilename,
+ size_t profilesize)
+{
+ int error = -EINVAL;
+ struct aaprofile *profile = NULL;
+ struct subdomain *sd;
+ char *name = NULL;
+ unsigned long flags;
+
+ AA_DEBUG("%s: current %s(%d)\n",
+ __FUNCTION__, current->comm, current->pid);
+
+ /* strip leading white space */
+ while (profilesize && isspace(*profilename)) {
+ profilename++;
+ profilesize--;
+ }
+
+ if (profilesize == 0)
+ goto out;
+
+ /*
+ * Copy string to a new buffer so we guarantee it is zero
+ * terminated
+ */
+ name = kmalloc(profilesize + 1, GFP_KERNEL);
+
+ if (!name) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ strncpy(name, profilename, profilesize);
+ name[profilesize] = 0;
+
+ repeat:
+ if (strcmp(name, "unconstrained") != 0) {
+ profile = aa_profilelist_find(name);
+ if (!profile) {
+ AA_WARN("%s: Unable to switch task %s(%d) to profile"
+ "'%s'. No such profile.\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ name);
+
+ error = -EINVAL;
+ goto out;
+ }
+ }
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ sd = AA_SUBDOMAIN(p->security);
+
+ /* switch to unconstrained */
+ if (!profile) {
+ if (__aa_is_confined(sd)) {
+ AA_WARN("%s: Unconstraining task %s(%d) "
+ "profile %s active %s\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ aa_switch_unconfined(sd);
+ } else {
+ AA_WARN("%s: task %s(%d) "
+ "is already unconstrained\n",
+ __FUNCTION__, p->comm, p->pid);
+ }
+ } else {
+ if (!sd) {
+ /* this task was created before module was
+ * loaded, allocate a subdomain
+ */
+ AA_WARN("%s: task %s(%d) has no subdomain\n",
+ __FUNCTION__, p->comm, p->pid);
+
+ /* unlock so we can safely GFP_KERNEL */
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ sd = alloc_subdomain(p);
+ if (!sd) {
+ AA_WARN("%s: Unable to allocate subdomain for "
+ "task %s(%d). Cannot confine task to "
+ "profile %s\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ name);
+
+ error = -ENOMEM;
+ put_aaprofile(profile);
+
+ goto out;
+ }
+
+ spin_lock_irqsave(&sd_lock, flags);
+ if (!AA_SUBDOMAIN(p->security)) {
+ p->security = sd;
+ } else { /* race */
+ free_subdomain(sd);
+ sd = AA_SUBDOMAIN(p->security);
+ }
+ }
+
+ /* ensure the profile hasn't been replaced */
+
+ if (unlikely(profile->isstale)) {
+ WARN_ON(profile == null_complain_profile);
+
+ /* drop refcnt obtained from earlier get_aaprofile */
+ put_aaprofile(profile);
+ profile = aa_profilelist_find(name);
+
+ if (!profile) {
+ /* Race, profile was removed. */
+ spin_unlock_irqrestore(&sd_lock, flags);
+ goto repeat;
+ }
+ }
+
+ /* we do not do a normal task replace since we are not
+ * replacing with the same profile.
+ * If existing process is in a hat, it will be moved
+ * into the new parent profile, even if this new
+ * profile has a identical named hat.
+ */
+
+ AA_WARN("%s: Switching task %s(%d) "
+ "profile %s active %s to new profile %s\n",
+ __FUNCTION__,
+ p->comm, p->pid,
+ sd->active ? BASE_PROFILE(sd->active)->name :
+ "unconstrained",
+ sd->active ? sd->active->name : "unconstrained",
+ name);
+
+ aa_switch(sd, profile);
+
+ put_aaprofile(profile); /* drop ref we obtained above
+ * from aa_profilelist_find
+ */
+
+ /* Reset magic in case we were in a subhat before
+ * This is the only case where we zero the magic after
+ * calling aa_switch
+ */
+ sd->hat_magic = 0;
+ }
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+out:
+ kfree(name);
+
+ return error;
+}
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:50:34 UTC
Permalink
This patch exports the namespace_sem semaphore.

The shared subtree patches which went into 2.6.15-rc1 replaced the old
namespace semaphore which used to be per namespace (and visible) with a
new single static semaphore.

The reason for this change is that currently visibility of vfsmount information
to the LSM hooks is fairly patchy. Either there is no passed parameter or
it can be NULL. For the case of the former, several LSM hooks that we
require to mediate have no vfsmount/nameidata passed. We previously (mis)used
the visibility of the old per namespace semaphore to walk the processes
namespace looking for vfsmounts with a root dentry matching the dentry we were
trying to mediate.

Clearly this is not viable long term strategy and changes working towards
passing a vfsmount to all relevant LSM hooks would seem necessary (and also
useful for other users of LSM). Alternative suggestions and ideas are welcomed.

Signed-off-by: Tony Jones <***@suse.de>

---
fs/namespace.c | 3 ++-
include/linux/namespace.h | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)

--- linux-2.6.17-rc1.orig/fs/namespace.c
+++ linux-2.6.17-rc1/fs/namespace.c
@@ -46,7 +46,8 @@
static struct list_head *mount_hashtable __read_mostly;
static int hash_mask __read_mostly, hash_bits __read_mostly;
static kmem_cache_t *mnt_cache __read_mostly;
-static struct rw_semaphore namespace_sem;
+struct rw_semaphore namespace_sem;
+EXPORT_SYMBOL_GPL(namespace_sem);

/* /sys/fs */
decl_subsys(fs, NULL, NULL);
--- linux-2.6.17-rc1.orig/include/linux/namespace.h
+++ linux-2.6.17-rc1/include/linux/namespace.h
@@ -5,6 +5,8 @@
#include <linux/mount.h>
#include <linux/sched.h>

+extern struct rw_semaphore namespace_sem;
+
struct namespace {
atomic_t count;
struct vfsmount * root;
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Christoph Hellwig
2006-04-19 22:10:38 UTC
Permalink
Post by Tony Jones
This patch exports the namespace_sem semaphore.
The shared subtree patches which went into 2.6.15-rc1 replaced the old
namespace semaphore which used to be per namespace (and visible) with a
new single static semaphore.
The reason for this change is that currently visibility of vfsmount information
to the LSM hooks is fairly patchy. Either there is no passed parameter or
it can be NULL. For the case of the former, several LSM hooks that we
require to mediate have no vfsmount/nameidata passed. We previously (mis)used
the visibility of the old per namespace semaphore to walk the processes
namespace looking for vfsmounts with a root dentry matching the dentry we were
trying to mediate.
Clearly this is not viable long term strategy and changes working towards
passing a vfsmount to all relevant LSM hooks would seem necessary (and also
useful for other users of LSM). Alternative suggestions and ideas are welcomed.
Just don't do it. No module has any business looking in there, and no
non-modular code outside a few files in fs/ either.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-20 12:39:02 UTC
Permalink
Post by Tony Jones
This patch exports the namespace_sem semaphore.
The shared subtree patches which went into 2.6.15-rc1 replaced the old
namespace semaphore which used to be per namespace (and visible) with a
new single static semaphore.
The reason for this change is that currently visibility of vfsmount information
to the LSM hooks is fairly patchy. Either there is no passed parameter or
it can be NULL. For the case of the former, several LSM hooks that we
require to mediate have no vfsmount/nameidata passed. We previously (mis)used
the visibility of the old per namespace semaphore to walk the processes
namespace looking for vfsmounts with a root dentry matching the dentry we were
trying to mediate.
Clearly this is not viable long term strategy and changes working towards
passing a vfsmount to all relevant LSM hooks would seem necessary (and also
useful for other users of LSM). Alternative suggestions and ideas are welcomed.
The alternative I would recommend is to not use LSM. It isn't suitable
for your path-based approach. If your path-based approach is deemed
legitimate, then introduce new hooks at the proper point in processing
where the information you need is available.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Serge E. Hallyn
2006-04-20 12:46:47 UTC
Permalink
Post by Stephen Smalley
Post by Tony Jones
This patch exports the namespace_sem semaphore.
The shared subtree patches which went into 2.6.15-rc1 replaced the old
namespace semaphore which used to be per namespace (and visible) with a
new single static semaphore.
The reason for this change is that currently visibility of vfsmount information
to the LSM hooks is fairly patchy. Either there is no passed parameter or
it can be NULL. For the case of the former, several LSM hooks that we
require to mediate have no vfsmount/nameidata passed. We previously (mis)used
the visibility of the old per namespace semaphore to walk the processes
namespace looking for vfsmounts with a root dentry matching the dentry we were
trying to mediate.
Clearly this is not viable long term strategy and changes working towards
passing a vfsmount to all relevant LSM hooks would seem necessary (and also
useful for other users of LSM). Alternative suggestions and ideas are welcomed.
The alternative I would recommend is to not use LSM. It isn't suitable
for your path-based approach. If your path-based approach is deemed
legitimate, then introduce new hooks at the proper point in processing
where the information you need is available.
Whoa, so now LSM is not for access control?

Of course if SuSE follows Al Viro's suggestion of using namespaces
(effectively using capabilities in their traditional sense), they'll use
fewer hooks, but they'll still need to prevent leaking of fd's accross
namespaces, for instance.

-serge
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-20 12:05:35 UTC
Permalink
Post by Serge E. Hallyn
Post by Stephen Smalley
Post by Tony Jones
This patch exports the namespace_sem semaphore.
The shared subtree patches which went into 2.6.15-rc1 replaced the old
namespace semaphore which used to be per namespace (and visible) with a
new single static semaphore.
The reason for this change is that currently visibility of vfsmount information
to the LSM hooks is fairly patchy. Either there is no passed parameter or
it can be NULL. For the case of the former, several LSM hooks that we
require to mediate have no vfsmount/nameidata passed. We previously (mis)used
the visibility of the old per namespace semaphore to walk the processes
namespace looking for vfsmounts with a root dentry matching the dentry we were
trying to mediate.
Clearly this is not viable long term strategy and changes working towards
passing a vfsmount to all relevant LSM hooks would seem necessary (and also
useful for other users of LSM). Alternative suggestions and ideas are welcomed.
The alternative I would recommend is to not use LSM. It isn't suitable
for your path-based approach. If your path-based approach is deemed
legitimate, then introduce new hooks at the proper point in processing
where the information you need is available.
Whoa, so now LSM is not for access control?
That isn't what I said, although I see that my phrasing wasn't clear. I
said it wasn't suitable for a path-based approach. That is fairly clear
from the hook placements and interfaces, and from the contortions that
AppArmor has to go through in order to obtain the paths, and the number
of times it ends up calling d_path on a single syscall. Now "new hooks"
_could_ be new LSM hooks, I suppose, but my point was that it is a
mistake to try to use the existing LSM VFS hooks for this purpose - they
are in the wrong place for it, and no amount of munging will fix that.
Make sense?
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Serge E. Hallyn
2006-04-20 13:21:28 UTC
Permalink
Post by Stephen Smalley
Post by Serge E. Hallyn
Post by Stephen Smalley
Post by Tony Jones
This patch exports the namespace_sem semaphore.
The shared subtree patches which went into 2.6.15-rc1 replaced the old
namespace semaphore which used to be per namespace (and visible) with a
new single static semaphore.
The reason for this change is that currently visibility of vfsmount information
to the LSM hooks is fairly patchy. Either there is no passed parameter or
it can be NULL. For the case of the former, several LSM hooks that we
require to mediate have no vfsmount/nameidata passed. We previously (mis)used
the visibility of the old per namespace semaphore to walk the processes
namespace looking for vfsmounts with a root dentry matching the dentry we were
trying to mediate.
Clearly this is not viable long term strategy and changes working towards
passing a vfsmount to all relevant LSM hooks would seem necessary (and also
useful for other users of LSM). Alternative suggestions and ideas are welcomed.
The alternative I would recommend is to not use LSM. It isn't suitable
for your path-based approach. If your path-based approach is deemed
legitimate, then introduce new hooks at the proper point in processing
where the information you need is available.
Whoa, so now LSM is not for access control?
That isn't what I said, although I see that my phrasing wasn't clear. I
said it wasn't suitable for a path-based approach. That is fairly clear
from the hook placements and interfaces, and from the contortions that
AppArmor has to go through in order to obtain the paths, and the number
of times it ends up calling d_path on a single syscall. Now "new hooks"
.
Post by Stephen Smalley
_could_ be new LSM hooks, I suppose, but my point was that it is a
mistake to try to use the existing LSM VFS hooks for this purpose - they
are in the wrong place for it, and no amount of munging will fix that.
Make sense?
Yup, that (.) seems a pursuasive hint.

Tony, do you have any performance measurements? Both for unconfined and
confined apps? Presumably unconfined processes should have 0 performance
hit, right?

-serge
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-20 12:48:38 UTC
Permalink
Post by Serge E. Hallyn
Tony, do you have any performance measurements? Both for unconfined and
confined apps? Presumably unconfined processes should have 0 performance
hit, right?
Preferably something that exercises open, mkdir, link... and friends
intensively, not just the old WebStone data that I've seen posted
before.

But you don't really need the benchmarks - just look at the code, and
think about the implications of allocating a page and calling d_path on
every permission(9) call (on every component) plus from the separate
hooks in the vfs_ helpers and further consider the impact of taking the
dcache lock all the time there. And look at the iterators being used in
aa_perm_dentry as well as the truly fun ones in aa_link. All because
they are doing it from LSM hooks that were never intended to be used
this way.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-20 12:58:52 UTC
Permalink
Post by Stephen Smalley
Post by Serge E. Hallyn
Tony, do you have any performance measurements? Both for unconfined and
confined apps? Presumably unconfined processes should have 0 performance
hit, right?
Preferably something that exercises open, mkdir, link... and friends
intensively, not just the old WebStone data that I've seen posted
before.
But you don't really need the benchmarks - just look at the code, and
think about the implications of allocating a page and calling d_path on
every permission(9) call (on every component) plus from the separate
hooks in the vfs_ helpers and further consider the impact of taking the
dcache lock all the time there. And look at the iterators being used in
aa_perm_dentry as well as the truly fun ones in aa_link. All because
they are doing it from LSM hooks that were never intended to be used
this way.
Ah, I have to correct the above - the mask filtering skips directory
traversal checking, so not every component I suppose. Which is
interesting for another reason. But performance situation still looks
fairly bad from a code POV, and the existing hooks still seem to be the
wrong place for this kind of processing/checking.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Linda A. Walsh
2006-04-20 22:11:51 UTC
Permalink
Post by Stephen Smalley
But you don't really need the benchmarks - just look at the code, and
think about the implications of allocating a page and calling d_path on
every permission(9) call (on every component) plus from the separate
hooks in the vfs_ helpers and further consider the impact of taking the
dcache lock all the time there. And look at the iterators being used in
aa_perm_dentry as well as the truly fun ones in aa_link. All because
they are doing it from LSM hooks that were never intended to be used
this way.
---
Agreed. The LSM hooks as they stand now are unsuitable
for AppArmor for the same reason they were unsuitable for auditing.
Linux isn't serious about security. If it was, it would have
the needed security calls. They were written, developed and tested
and benchmarked. Full auditing of every security relevant call
with full auditing turned on, had less than a 10% performance
hit doing a kernel build (while recording 5MBytes to disk/second
by the user-space audit daemon) -- that was using a 2x400MHz SMP
machine with 1 SCSI3 based disk (~30-35MB/s max transfer rate).
It was configurable at kernel build time to "totally go away" if
not used, to costing less than 1% (in the noise level) for compiled
in but turned off.

The *current* accepted way to get pathnames going into system
calls is to trap the syscall vector as audit currently does --
a method subject to race conditions. There is no way to implement
pathname-based security (or auditing) without providing hooks
in each of the relevant system calls after they have copied their
arguments from user space, safely into kernel space. Decoding
the arguments (including copying them from user space) twice allows
for a window during which the user-space arguments can still be
changed by a user-level process. You can't copy the arguments from
userspace, twice, and expect that the userspace memory will be
remain the same between the two "copies".

L


-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Christoph Hellwig
2006-04-20 23:05:51 UTC
Permalink
Post by Linda A. Walsh
The *current* accepted way to get pathnames going into system
calls is to trap the syscall vector as audit currently does --
It's not and it's never been. Please get a fucking clue instead of
posting your uninformed opinions to lkml.

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Linda A. Walsh
2006-04-21 01:29:03 UTC
Permalink
Post by Linda A. Walsh
The *current* accepted way to get pathnames going into system
calls is to trap the syscall vector as audit currently does --
It's not and it's never been. [suggestion deleted]
What is not? I'm looking at entry.S, and 2 ptrace.c's, one under
arch/i386/kernel and another under kernel. Perhaps we are talking
about different architectures? Referring to the i386 architecture,

entry.S has the system call table processing, no?
This is the the code from the sysenter call:

testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AU
DIT),TI_flags(%ebp)

That looks like a patch for SECCOMP, SYSCALL_EMU and AUDIT that goes
off to do special processing in the system sys_trace call. This calls the
ptrace function for every syscall, no? Doesn't that then call
kernel/ptrace.c(sys_ptrace), grab the system lock (that is
what lock_system() is for, isn't it?), which then calls the
arch-specific ptrace.c in 'arch/i386/kernel'? Or have I missed
something yet?

Now here is code from that ptrace.c:
------------
/* notification of system call entry/exit
* - triggered by current->work.syscall_trace
*/
__attribute__((regparm(3)))
int do_syscall_trace(struct pt_regs *regs, int entryexit)
{
... do sysemu related stuff...

/* do the secure computing check first */
if (!entryexit)
secure_computing(regs->orig_eax);

if (unlikely(current->audit_context)) {
if (entryexit)
audit_syscall_exit(current, AUDITSC_RESULT(regs->eax),
regs->eax);
--------------
Doesn't ptrace trap every syscall and call audit for every syscall
when audit is enabled?

Perhaps my wording was confusing? I'm sorry, I should have
said:

"The *current* accepted way to get pathnames going into system calls is
to put a trap in the syscall vector processing code to be indirectly
called through the ptrace call with every system call as audit currently
does..."?

Or is that not correct either? If not, could you please be more
specific in your objection instead of suggesting I get pointers on my sex
life?

Of course the above code brings up a 2nd question. Is it acceptable
for audit records to be lost, or if a system gets heavily loaded, isn't
it possible for audit_syscall to block waiting some place to record the
audit context? Wouldn't those call occur after the "lock_kernel();"
line in kernel/ptrace.c, Could it be holding the "big kernel lock"
(still) when it blocks? Or would audit drop the kernel lock before
blocking?

In my last linux audit driver implementation, I had it setup such
that the audited process would block, but the system could continue so
that "auditd" (a non audited process) could free up a buffer by "reading"
it thus unblocking any processes blocked on an audit call. But, not
hanging the system needlessly was one of my design goals.

Linda




-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Chris Wright
2006-04-21 02:09:29 UTC
Permalink
Post by Linda A. Walsh
"The *current* accepted way to get pathnames going into system calls is
to put a trap in the syscall vector processing code to be indirectly
called through the ptrace call with every system call as audit currently
does..."?
Or is that not correct either?
No it's not. See getname(9).

thanks,
-chris
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Linda Walsh
2006-04-21 05:10:38 UTC
Permalink
Post by Chris Wright
Post by Linda A. Walsh
"The *current* accepted way to get pathnames going into system calls is
to put a trap in the syscall vector processing code to be indirectly
called through the ptrace call with every system call as audit
currently does..."?
Or is that not correct either?
No it's not. See getname(9).
I'm familiar with the getname call, it's probably the case that
audit calls getname to do the actual copy from user->kernel space, I
haven't checked. But I can't find the manpage you are referring to.

I may be suffering from impaired "colloquialisms" in my writing, but
I was referring to the process of collecting pathnames for use in
a security policy (ex. audit, systrace or AppArmor) for the
kernel calls that take one or more pathnames being done via code
inserted into the system call code that is called with each system
call.

Whatever policy (audit, AppArmor, etc) is in place is then called
on every syscall and each policy then decides what actual
system calls it is interested in and then does call specific
argument processing to make a record of or enforce policy.

The argument processing would likely involve getname() to retrieve
the path from user space.

Is there something specific on the getname manpage you are
referring to or are we talking about the same thing?

Thanks for the clarification...:-)
Linda

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Arjan van de Ven
2006-04-23 12:11:13 UTC
Permalink
Post by Linda Walsh
Post by Chris Wright
Post by Linda A. Walsh
"The *current* accepted way to get pathnames going into system calls is
to put a trap in the syscall vector processing code to be indirectly
called through the ptrace call with every system call as audit
currently does..."?
Or is that not correct either?
No it's not. See getname(9).
I'm familiar with the getname call, it's probably the case that
audit calls getname to do the actual copy from user->kernel space, I
haven't checked. But I can't find the manpage you are referring to.
you CANNOT copy twice. If you copy twice you might as well not audit
since userspace can just change it inbetween. what audit does is use the
original ONE copy that the normal syscall does .


-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 14:02:02 UTC
Permalink
Post by Linda A. Walsh
The *current* accepted way to get pathnames going into system
calls is to trap the syscall vector as audit currently does --
a method subject to race conditions. There is no way to implement
pathname-based security (or auditing) without providing hooks
in each of the relevant system calls after they have copied their
arguments from user space, safely into kernel space. Decoding
the arguments (including copying them from user space) twice allows
for a window during which the user-space arguments can still be
changed by a user-level process. You can't copy the arguments from
userspace, twice, and expect that the userspace memory will be
remain the same between the two "copies".
They aren't being copied twice. Look at getname() in fs/namei.c, and
note the call to audit_getname(). The native Linux 2.6 audit framework
combines processing at entry/exit with certain hooks placed at key
locations to collect the necessary information, without requiring the
degree of invasiveness of the SGI CAPP auditing patches of long ago.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-20 19:45:03 UTC
Permalink
Post by Serge E. Hallyn
Tony, do you have any performance measurements? Both for unconfined and
confined apps? Presumably unconfined processes should have 0 performance
hit, right?
Hi Serge.

We have lmbench results. We had issues getting reproducability out of dbench
but need to look at it some more. The lmbench figures we presently have are
from the old code (reader writer lock). Results were good but we recently
converted to rcu for the reader dominated rw locks.

I'm not sure there is "0 performance hit for unconfined" as you have to check
for them being unconfined :) but it's low.

If you have other benchmarks we are open to suggestions. What has been used
to benchmark SELinux?

Anyways, we need to regenerate the results, we'll try and post in the next
couple days. ok?

Thanks

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Serge E. Hallyn
2006-04-20 20:16:08 UTC
Permalink
Post by Tony Jones
Post by Serge E. Hallyn
Tony, do you have any performance measurements? Both for unconfined and
confined apps? Presumably unconfined processes should have 0 performance
hit, right?
Hi Serge.
We have lmbench results. We had issues getting reproducability out of dbench
but need to look at it some more. The lmbench figures we presently have are
from the old code (reader writer lock). Results were good but we recently
converted to rcu for the reader dominated rw locks.
I'm not sure there is "0 performance hit for unconfined" as you have to check
for them being unconfined :) but it's low.
If you have other benchmarks we are open to suggestions. What has been used
to benchmark SELinux?
I typically use dbench, reaim, tbench, and kernbench, running each 20
times.
Post by Tony Jones
Anyways, we need to regenerate the results, we'll try and post in the next
couple days. ok?
Cool, thanks.

-serge
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
James Morris
2006-04-20 20:22:02 UTC
Permalink
Post by Tony Jones
We have lmbench results. We had issues getting reproducability out of dbench
but need to look at it some more.
Which filesystem were you running dbench on? IIRC, journaling plays
havoc with it and you need to use something like ext2 to get reliable
data.

(Even though dbench is generally frowned upon, it's useful in situations
like this where you're not tuning fs code or similar).



- James
--
James Morris
<***@namei.org>
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Linda Walsh
2006-04-20 21:50:32 UTC
Permalink
The alternative I would recommend is to not use LSM. It isn't suitable
for your path-based approach. If your path-based approach is deemed
legitimate, then introduce new hooks at the proper point in processing
where the information you need is available.
---
I thought LSM was supposed to provide the hooks to allow virtually
any access control scheme to be implemented? I've seen complaints
before on either here or the LSM list that one of the hurdles for
"legitimacy" was whether or not it fit on top of the current set of
LSM hooks. I also saw it asked whether or not LSM had been
designed around, primarily, the needs of SELinux and if it was
going to remain so. If it was, then why not remove all non-SELinux
hooks? If LSM is to support alternate security methods, it is
logical to believe that LSM was not implemented with calls to
support every desired security model people might want. There
are known, insecure, race conditions in linux auditing, for
example, due to lack of LSM hooks. This was a conscious
design decision made by the LSM majority over objections
of people who wanted greater flexibility to support security
mechanisms not supportable with the current set of hooks.

In regards to "legitimacy", while I share the reservations
of many people in using a path based approach to security, I
might point out that this model is a basic one integrated into
Windows NT (XP & later, 2k?). That doesn't mean it is "good",
but it certainly should add some weight to the claim of
"legitimacy". I.e. - it provides a "comfortable", known
security mechanism for people switching to Linux servers from
from "Windows Server 2003".

In the Windows approach, you can specify allowed and disallowed
paths by unique name and using wildcards. This allowed/disallowed
hash is checked before every program execution.

If you start with a large, multi-user system, and allow no
user-level mounts (they just sign in and can pick from a
limited menu of choices, the pathname approach can have some
merit. For example, one might have a security policy only
allowing execution of binaries in "/usr/bin". The employer
puts all of his "reservation-system" or "database-access" routines
in "/usr/bin" (or adds the app path(s) to the allowed hash).
The end users run the allowed binaries and that's it.

I'm not saying it's an approach I would find useful to control
security on my systems, but I can see a potential usefulness
for it, in that it is relatively easy for people to understand,
setup and use.

Linda W
Al Viro
2006-04-20 21:56:46 UTC
Permalink
Post by Linda Walsh
any access control scheme to be implemented? I've seen complaints
before on either here or the LSM list that one of the hurdles for
"legitimacy" was whether or not it fit on top of the current set of
LSM hooks. I also saw it asked whether or not LSM had been
designed
... and the answer is obviously "no". AFAICS, that was a way to get
around Linus' "at least decide on a common set of core kernel modifications"
without any kind of thinking being involved.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
James Morris
2006-04-20 23:54:07 UTC
Permalink
Post by Al Viro
Post by Linda Walsh
any access control scheme to be implemented? I've seen complaints
before on either here or the LSM list that one of the hurdles for
"legitimacy" was whether or not it fit on top of the current set of
LSM hooks. I also saw it asked whether or not LSM had been
designed
... and the answer is obviously "no". AFAICS, that was a way to get
around Linus' "at least decide on a common set of core kernel modifications"
without any kind of thinking being involved.
For reference, here are the original comments from Linus which were used
to conceive LSM:
http://marc.theaimsgroup.com/?l=linux-security-module&m=98706471912438&w=2

In a nutshell, Linus did not want to have to choose a security model.

In my view, the generic, correctly abstracted mechanism was actually
SELinux all along, and unfortunately, only a few people really understood
that then. SELinux was itself designed to allow different security models
to be composed, with clean separation of models, policy and enforcement
mechanism.

LSM was somewhat designed around SELinux, but necessarily lacking the
stronger semantics of SELinux, to allow other similar schemes to be
plugged in (the first significant example of which other than SELinux, has
only just appeared on lkml five years later).


- James
--
James Morris
<***@namei.org>
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 13:59:13 UTC
Permalink
Post by Linda Walsh
The alternative I would recommend is to not use LSM. It isn't suitable
for your path-based approach. If your path-based approach is deemed
legitimate, then introduce new hooks at the proper point in processing
where the information you need is available.
---
I thought LSM was supposed to provide the hooks to allow virtually
any access control scheme to be implemented?
The first question is whether a path-based mechanism is suitable for the
kernel at all. Not my call to make, but seems to run counter to the
Unix and more so the Linux model, and to past discussions on
linux-fsdevel and linux-kernel.

If a path-based mechanism is suitable, then the next question is whether
LSM is suitable as a means of implementing such a mechanism. At
present, I would argue that it is not - the hook placement and
interfaces are not well suited to it (despite being originally proposed
by the WireX folks who worked on SubDomain/AppArmor), and the current
AppArmor implementation requires significant contortions to work around
the interface mismatch. Which would suggest that they need to propose
changes to LSM (e.g. new hooks at more suitable locations) first.
Post by Linda Walsh
I've seen complaints
before on either here or the LSM list that one of the hurdles for
"legitimacy" was whether or not it fit on top of the current set of
LSM hooks.
I don't recall that one; submitting new hook proposals is ok as long as
there is a user that will also be submitted. SELinux itself has needed
to extend the LSM interface over time.
Post by Linda Walsh
I also saw it asked whether or not LSM had been
designed around, primarily, the needs of SELinux and if it was
going to remain so.
SELinux was and remains the primary user, so that obviously has
influence, but as I've noted before, the original VFS hooks were first
proposed by the WireX folks, and they were active participants during
LSM development.
Post by Linda Walsh
If it was, then why not remove all non-SELinux
hooks?
That is actually a good idea. They can always be added back if a
genuine user comes along. SELinux also has some stubs that should be
dropped at the same time.
Post by Linda Walsh
If LSM is to support alternate security methods, it is
logical to believe that LSM was not implemented with calls to
support every desired security model people might want. There
are known, insecure, race conditions in linux auditing, for
example, due to lack of LSM hooks. This was a conscious
design decision made by the LSM majority over objections
of people who wanted greater flexibility to support security
mechanisms not supportable with the current set of hooks.
I think you haven't looked at the native Linux 2.6 audit implementation
very closely. LSM wasn't suitable for audit. The namei code and other
parts of the kernel have been hooked to call into the audit system to
collect information as needed.
Post by Linda Walsh
In regards to "legitimacy", while I share the reservations
of many people in using a path based approach to security, I
might point out that this model is a basic one integrated into
Windows NT (XP & later, 2k?). That doesn't mean it is "good",
but it certainly should add some weight to the claim of
"legitimacy". I.e. - it provides a "comfortable", known
security mechanism for people switching to Linux servers from
from "Windows Server 2003".
In the Windows approach, you can specify allowed and disallowed
paths by unique name and using wildcards. This allowed/disallowed
hash is checked before every program execution.
Do you know how they implement it? The question is not whether
path-based configuration in userspace is ok; it is whether the kernel
mechanism should be relying on pathnames. There are also much saner
implementation approaches for name-based schemes than calling d_path to
generate the full path and checking that against a profile on each open;
DTE was one example.
Post by Linda Walsh
If you start with a large, multi-user system, and allow no
user-level mounts (they just sign in and can pick from a
limited menu of choices, the pathname approach can have some
merit. For example, one might have a security policy only
allowing execution of binaries in "/usr/bin". The employer
puts all of his "reservation-system" or "database-access" routines
in "/usr/bin" (or adds the app path(s) to the allowed hash).
The end users run the allowed binaries and that's it.
SELinux can express such restrictions via its TE configuration already.
Or you can implement this kind of mechanism in other ways, but it
doesn't require the kernel to be generating and checking pathnames.
Post by Linda Walsh
I'm not saying it's an approach I would find useful to control
security on my systems, but I can see a potential usefulness
for it, in that it is relatively easy for people to understand,
setup and use.
Which is fine for userspace tools, but doesn't justify it as the kernel
mechanism.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:50:18 UTC
Permalink
This patch adds AppArmor support to the audit subsystem.

It creates id 1500 (already included in the the upstream auditd package) for
AppArmor messages.

It also exports the audit_log_vformat function (analagous to having both
printk and vprintk exported).

Signed-off-by: Tony Jones <***@suse.de>

---
include/linux/audit.h | 5 +++++
kernel/audit.c | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)

--- linux-2.6.17-rc1.orig/include/linux/audit.h
+++ linux-2.6.17-rc1/include/linux/audit.h
@@ -95,6 +95,8 @@
#define AUDIT_LAST_KERN_ANOM_MSG 1799
#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */

+#define AUDIT_AA 1500 /* AppArmor audit */
+
#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */

/* Rule flags */
@@ -349,6 +351,9 @@
__attribute__((format(printf,4,5)));

extern struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type);
+extern void audit_log_vformat(struct audit_buffer *ab,
+ const char *fmt, va_list args)
+ __attribute__((format(printf,2,0)));
extern void audit_log_format(struct audit_buffer *ab,
const char *fmt, ...)
__attribute__((format(printf,2,3)));
--- linux-2.6.17-rc1.orig/kernel/audit.c
+++ linux-2.6.17-rc1/kernel/audit.c
@@ -797,7 +797,7 @@
* will be called a second time. Currently, we assume that a printk
* can't format message larger than 1024 bytes, so we don't either.
*/
-static void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
+void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
va_list args)
{
int len, avail;
@@ -999,4 +999,5 @@
EXPORT_SYMBOL(audit_log_start);
EXPORT_SYMBOL(audit_log_end);
EXPORT_SYMBOL(audit_log_format);
+EXPORT_SYMBOL(audit_log_vformat);
EXPORT_SYMBOL(audit_log);
Amy Griffis
2006-04-21 21:21:09 UTC
Permalink
Tony Jones wrote: [Wed Apr 19 2006, 01:50:18PM EDT]
Post by Tony Jones
This patch adds AppArmor support to the audit subsystem.
It creates id 1500 (already included in the the upstream auditd package) for
AppArmor messages.
It also exports the audit_log_vformat function (analagous to having both
printk and vprintk exported).
linux-audit (cc'd) will likely want to review these changes.
Post by Tony Jones
---
include/linux/audit.h | 5 +++++
kernel/audit.c | 3 ++-
2 files changed, 7 insertions(+), 1 deletion(-)
--- linux-2.6.17-rc1.orig/include/linux/audit.h
+++ linux-2.6.17-rc1/include/linux/audit.h
@@ -95,6 +95,8 @@
#define AUDIT_LAST_KERN_ANOM_MSG 1799
#define AUDIT_ANOM_PROMISCUOUS 1700 /* Device changed promiscuous mode */
+#define AUDIT_AA 1500 /* AppArmor audit */
+
#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */
/* Rule flags */
@@ -349,6 +351,9 @@
__attribute__((format(printf,4,5)));
extern struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type);
+extern void audit_log_vformat(struct audit_buffer *ab,
+ const char *fmt, va_list args)
+ __attribute__((format(printf,2,0)));
extern void audit_log_format(struct audit_buffer *ab,
const char *fmt, ...)
__attribute__((format(printf,2,3)));
--- linux-2.6.17-rc1.orig/kernel/audit.c
+++ linux-2.6.17-rc1/kernel/audit.c
@@ -797,7 +797,7 @@
* will be called a second time. Currently, we assume that a printk
* can't format message larger than 1024 bytes, so we don't either.
*/
-static void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
+void audit_log_vformat(struct audit_buffer *ab, const char *fmt,
va_list args)
{
int len, avail;
@@ -999,4 +999,5 @@
EXPORT_SYMBOL(audit_log_start);
EXPORT_SYMBOL(audit_log_end);
EXPORT_SYMBOL(audit_log_format);
+EXPORT_SYMBOL(audit_log_vformat);
EXPORT_SYMBOL(audit_log);
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Steve Grubb
2006-04-22 00:13:52 UTC
Permalink
Post by Amy Griffis
linux-audit (cc'd) will likely want to review these changes.
Yes, I second that. Tony, please cc audit patches to linux-audit mail list so
we can see them. That said, I did tell Tony they could use message type
numbers 1500 - 1600 for AppArmor if they need it.

-Steve
Tony Jones
2006-04-22 00:19:14 UTC
Permalink
Post by Steve Grubb
Post by Amy Griffis
linux-audit (cc'd) will likely want to review these changes.
Yes, I second that. Tony, please cc audit patches to linux-audit mail list so
we can see them. That said, I did tell Tony they could use message type
numbers 1500 - 1600 for AppArmor if they need it.
Sorry, I thought I'd bounced this one patch in the series to the audit list.
I meant to. One more thing lost in the noise. Apologies.

1500 should already be reserved for apparmor userside. Only change is to
enable it kernelside plus of course the one more symbol export to bloat the
kernel image. Export of the vformat call is to make it analagous to vprintk.
Sometimes it's more convenient to have a single point of logging (as we do)
and you need to log data which is in va_list format.

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:49:29 UTC
Permalink
Implements the lsm interface used by AppArmor.

The code composes the functionality provided by commoncap therefore there
is no requirement for it to stack with the capability module.

See linux/include/security.h for a full description of all the LSM hooks.

Consistency of the subdomain (task) data is implemented on the reader side
via rcu. Logical consistency across profile replacement/removal and change
hat is provided via the spinlock sd_lock. Since profile manipulation and
change_hat are infrequent, most syscall accesses requires no spin lock.

Certain syscalls are prevented for confined processes. These are:
ptrace
mount
umount
sysctl writes also require CAP_SYS_ADMIN

File access checks are performed when a file is initially opened
(inode_permission) and cached to avoid revalidation unless where necessary
(passing descriptors between tasks confined with differing profiles, and
profile replacement, for example). Further patches are in development
to support caching of multiple profiles against an open file to minimise
the need for subsequent revalidation across profiles.


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/lsm.c | 840 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 840 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/lsm.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 2002-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
+ *
+ * Immunix AppArmor LSM interface
+ */
+
+#include <linux/security.h>
+#include <linux/module.h>
+#include <linux/mman.h>
+
+#include "apparmor.h"
+#include "inline.h"
+
+/* struct subdomain write update lock (read side is RCU). */
+spinlock_t sd_lock = SPIN_LOCK_UNLOCKED;
+
+/* Flag values, also controllable via apparmorfs/control.
+ * We explicitly do not allow these to be modifiable when exported via
+ * /sys/modules/parameters, as we want to do additional mediation and
+ * don't want to add special path code. */
+
+/* Complain mode -- in complain mode access failures result in auditing only
+ * and task is allowed access. audit events are processed by userspace to
+ * generate policy. Default is 'enforce' (0).
+ * Value is also togglable per profile and referenced when global value is
+ * enforce.
+ */
+int apparmor_complain = 0;
+module_param_named(complain, apparmor_complain, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_complain, "Toggle AppArmor complain mode");
+
+/* Debug mode */
+int apparmor_debug = 0;
+module_param_named(debug, apparmor_debug, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_debug, "Toggle AppArmor debug mode");
+
+/* Audit mode */
+int apparmor_audit = 0;
+module_param_named(audit, apparmor_audit, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_audit, "Toggle AppArmor audit mode");
+
+/* Syscall logging mode */
+int apparmor_logsyscall = 0;
+module_param_named(logsyscall, apparmor_logsyscall, int, S_IRUSR);
+MODULE_PARM_DESC(apparmor_logsyscall, "Toggle AppArmor logsyscall mode");
+
+#ifndef MODULE
+static int __init aa_getopt_complain(char *str)
+{
+ get_option(&str, &apparmor_complain);
+ return 1;
+}
+__setup("apparmor_complain=", aa_getopt_complain);
+
+static int __init aa_getopt_debug(char *str)
+{
+ get_option(&str, &apparmor_debug);
+ return 1;
+}
+__setup("apparmor_debug=", aa_getopt_debug);
+
+static int __init aa_getopt_audit(char *str)
+{
+ get_option(&str, &apparmor_audit);
+ return 1;
+}
+__setup("apparmor_audit=", aa_getopt_audit);
+
+static int __init aa_getopt_logsyscall(char *str)
+{
+ get_option(&str, &apparmor_logsyscall);
+ return 1;
+}
+__setup("apparmor_logsyscall=", aa_getopt_logsyscall);
+#endif
+
+static int apparmor_ptrace(struct task_struct *parent,
+ struct task_struct *child)
+{
+ int error;
+ struct aaprofile *active;
+
+ error = cap_ptrace(parent, child);
+
+ active = get_active_aaprofile();
+
+ if (!error && active) {
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "ptrace");
+ WARN_ON(error != -EPERM);
+ }
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_capget(struct task_struct *target,
+ kernel_cap_t *effective,
+ kernel_cap_t *inheritable,
+ kernel_cap_t *permitted)
+{
+ return cap_capget(target, effective, inheritable, permitted);
+}
+
+static int apparmor_capset_check(struct task_struct *target,
+ kernel_cap_t *effective,
+ kernel_cap_t *inheritable,
+ kernel_cap_t *permitted)
+{
+ return cap_capset_check(target, effective, inheritable, permitted);
+}
+
+static void apparmor_capset_set(struct task_struct *target,
+ kernel_cap_t *effective,
+ kernel_cap_t *inheritable,
+ kernel_cap_t *permitted)
+{
+ cap_capset_set(target, effective, inheritable, permitted);
+ return;
+}
+
+static int apparmor_capable(struct task_struct *tsk, int cap)
+{
+ int error;
+
+ /* cap_capable returns 0 on success, else -EPERM */
+ error = cap_capable(tsk, cap);
+
+ if (error == 0) {
+ struct aaprofile *active;
+
+ active = get_task_active_aaprofile(tsk);
+
+ if (active)
+ error = aa_capability(active, cap);
+
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_sysctl(struct ctl_table *table, int op)
+{
+ int error = 0;
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+
+ if ((op & 002) && active && !capable(CAP_SYS_ADMIN)) {
+ error = aa_audit_syscallreject(active, GFP_KERNEL,
+ "sysctl (write)");
+ WARN_ON(error != -EPERM);
+ }
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_syslog(int type)
+{
+ return cap_syslog(type);
+}
+
+static int apparmor_netlink_send(struct sock *sk, struct sk_buff *skb)
+{
+ return cap_netlink_send(sk, skb);
+}
+
+static int apparmor_netlink_recv(struct sk_buff *skb)
+{
+ return cap_netlink_recv(skb);
+}
+
+static void apparmor_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
+{
+ cap_bprm_apply_creds(bprm, unsafe);
+ return;
+}
+
+static int apparmor_bprm_set_security(struct linux_binprm *bprm)
+{
+ /* handle capability bits with setuid, etc */
+ cap_bprm_set_security(bprm);
+ /* already set based on script name */
+ if (bprm->sh_bang)
+ return 0;
+ return aa_register(bprm->file);
+}
+
+static int apparmor_sb_mount(char *dev_name, struct nameidata *nd, char *type,
+ unsigned long flags, void *data)
+{
+ int error = 0;
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+
+ if (active) {
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "mount");
+ WARN_ON(error != -EPERM);
+ }
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_umount(struct vfsmount *mnt, int flags)
+{
+ int error = 0;
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+
+ if (active) {
+ error = aa_audit_syscallreject(active, GFP_KERNEL, "umount");
+ WARN_ON(error != -EPERM);
+ }
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_mkdir(struct inode *inode, struct dentry *dentry,
+ int mask)
+{
+ struct aaprofile *active;
+ int error = 0;
+
+ active = get_active_aaprofile();
+
+ if (active)
+ error = aa_perm_dir(active, dentry, aa_dir_mkdir);
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_rmdir(struct inode *inode, struct dentry *dentry)
+{
+ struct aaprofile *active;
+ int error = 0;
+
+ active = get_active_aaprofile();
+
+ if (active)
+ error = aa_perm_dir(active, dentry, aa_dir_rmdir);
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_create(struct inode *inode, struct dentry *dentry,
+ int mask)
+{
+ struct aaprofile *active;
+ int error = 0;
+
+ active = get_active_aaprofile();
+
+ /* At a minimum, need write perm to create */
+ if (active)
+ error = aa_perm_dentry(active, dentry, MAY_WRITE);
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_link(struct dentry *old_dentry, struct inode *inode,
+ struct dentry *new_dentry)
+{
+ int error = 0;
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+
+ if (active)
+ error = aa_link(active, new_dentry, old_dentry);
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_unlink(struct inode *inode, struct dentry *dentry)
+{
+ struct aaprofile *active;
+ int error = 0;
+
+ active = get_active_aaprofile();
+
+ if (active)
+ error = aa_perm_dentry(active, dentry, MAY_WRITE);
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_mknod(struct inode *inode, struct dentry *dentry,
+ int mode, dev_t dev)
+{
+ struct aaprofile *active;
+ int error = 0;
+
+ active = get_active_aaprofile();
+
+ if (active)
+ error = aa_perm_dentry(active, dentry, MAY_WRITE);
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_rename(struct inode *old_inode,
+ struct dentry *old_dentry,
+ struct inode *new_inode,
+ struct dentry *new_dentry)
+{
+ struct aaprofile *active;
+ int error = 0;
+
+ active = get_active_aaprofile();
+
+ if (active) {
+ error = aa_perm_dentry(active, old_dentry, MAY_READ |
+ MAY_WRITE);
+
+ if (!error)
+ error = aa_perm_dentry(active, new_dentry,
+ MAY_WRITE);
+ }
+
+ put_aaprofile(active);
+
+ return error;
+}
+
+static int apparmor_inode_permission(struct inode *inode, int mask,
+ struct nameidata *nd)
+{
+ int error = 0;
+
+ /* Do not perform check on pipes or sockets
+ * Same as apparmor_file_permission
+ */
+ if (VALID_FSTYPE(inode)) {
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ if (active)
+ error = aa_perm_nameidata(active, nd, mask);
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_inode_setattr(struct dentry *dentry, struct iattr *iattr)
+{
+ int error = 0;
+
+ if (VALID_FSTYPE(dentry->d_inode)) {
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ /*
+ * Mediate any attempt to change attributes of a file
+ * (chmod, chown, chgrp, etc)
+ */
+ if (active)
+ error = aa_attr(active, dentry, iattr);
+
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_inode_setxattr(struct dentry *dentry, char *name,
+ void *value, size_t size, int flags)
+{
+ int error = 0;
+
+ if (VALID_FSTYPE(dentry->d_inode)) {
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ if (active)
+ error = aa_xattr(active, dentry, name, aa_xattr_set);
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_inode_getxattr(struct dentry *dentry, char *name)
+{
+ int error = 0;
+
+ if (VALID_FSTYPE(dentry->d_inode)) {
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ if (active)
+ error = aa_xattr(active, dentry, name, aa_xattr_get);
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+static int apparmor_inode_listxattr(struct dentry *dentry)
+{
+ int error = 0;
+
+ if (VALID_FSTYPE(dentry->d_inode)) {
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ if (active)
+ error = aa_xattr(active, dentry, NULL, aa_xattr_list);
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_inode_removexattr(struct dentry *dentry, char *name)
+{
+ int error = 0;
+
+ if (VALID_FSTYPE(dentry->d_inode)) {
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ if (active)
+ error = aa_xattr(active, dentry, name,
+ aa_xattr_remove);
+ put_aaprofile(active);
+ }
+
+ return error;
+}
+
+static int apparmor_file_permission(struct file *file, int mask)
+{
+ struct aaprofile *active;
+ struct aaprofile *f_profile;
+ int error = 0;
+
+ f_profile = AA_PROFILE(file->f_security);
+ /* bail out early if this isn't a mediated file */
+ if (!(f_profile && VALID_FSTYPE(file->f_dentry->d_inode)))
+ goto out;
+
+ active = get_active_aaprofile();
+ if (active && f_profile != active)
+ error = aa_perm(active, file->f_dentry, file->f_vfsmnt,
+ mask & (MAY_EXEC | MAY_WRITE | MAY_READ));
+ put_aaprofile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_file_alloc_security(struct file *file)
+{
+ struct aaprofile *active;
+
+ active = get_active_aaprofile();
+ file->f_security = get_aaprofile(active);
+ put_aaprofile(active);
+
+ return 0;
+}
+
+static void apparmor_file_free_security(struct file *file)
+{
+ struct aaprofile *p = AA_PROFILE(file->f_security);
+ put_aaprofile(p);
+}
+
+static int apparmor_file_mmap(struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags)
+{
+ int error = 0, mask = 0;
+ struct aaprofile *active;
+
+ if (!file)
+ goto out;
+
+ active = get_active_aaprofile();
+
+ 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 |= MAY_EXEC;
+
+ AA_DEBUG("%s: 0x%x\n", __FUNCTION__, mask);
+
+ error = aa_perm(active, file->f_dentry, file->f_vfsmnt, mask);
+
+ put_aaprofile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_task_alloc_security(struct task_struct *p)
+{
+ return aa_fork(p);
+}
+
+static void apparmor_task_free_security(struct task_struct *p)
+{
+ aa_release(p);
+}
+
+static int apparmor_task_post_setuid(uid_t id0, uid_t id1, uid_t id2,
+ int flags)
+{
+ return cap_task_post_setuid(id0, id1, id2, flags);
+}
+
+static void apparmor_task_reparent_to_init(struct task_struct *p)
+{
+ cap_task_reparent_to_init(p);
+ return;
+}
+
+static int apparmor_getprocattr(struct task_struct *p, char *name, void *value,
+ size_t size)
+{
+ int error;
+ struct aaprofile *active;
+ char *str = value;
+
+ /* Subdomain only supports the "current" process attribute */
+ if (strcmp(name, "current") != 0) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ if (!size) {
+ error = -ERANGE;
+ goto out;
+ }
+
+ /* must be task querying itself or admin */
+ if (current != p && !capable(CAP_SYS_ADMIN)) {
+ error = -EPERM;
+ goto out;
+ }
+
+ active = get_task_active_aaprofile(p);
+ error = aa_getprocattr(active, str, size);
+ put_aaprofile(active);
+
+out:
+ return error;
+}
+
+static int apparmor_setprocattr(struct task_struct *p, char *name, void *value,
+ size_t size)
+{
+ const char *cmd_changehat = "changehat ",
+ *cmd_setprofile = "setprofile ";
+
+ int error = -EACCES; /* default to a perm denied */
+ char *cmd = (char *)value;
+
+ /* only support messages to current */
+ if (strcmp(name, "current") != 0) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ if (!size) {
+ error = -ERANGE;
+ goto out;
+ }
+
+ /* CHANGE HAT -- switch task into a subhat (subprofile) if defined */
+ if (size > strlen(cmd_changehat) &&
+ strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) {
+ char *hatinfo = cmd + strlen(cmd_changehat);
+ size_t infosize = size - strlen(cmd_changehat);
+
+ /* Only the current process may change it's hat */
+ if (current != p) {
+ AA_WARN("%s: Attempt by foreign task %s(%d) "
+ "[user %d] to changehat of task %s(%d)\n",
+ __FUNCTION__,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+
+ error = -EACCES;
+ goto out;
+ }
+
+ error = aa_setprocattr_changehat(hatinfo, infosize);
+ if (error == 0)
+ /* success, set return to #bytes in orig request */
+ error = size;
+
+ /* SET NEW PROFILE */
+ } else if (size > strlen(cmd_setprofile) &&
+ strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) {
+ struct aaprofile *active;
+
+ /* only an unconfined process with admin capabilities
+ * may change the profile of another task
+ */
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ AA_WARN("%s: Unprivileged attempt by task %s(%d) "
+ "[user %d] to assign profile to task %s(%d)\n",
+ __FUNCTION__,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+ error = -EACCES;
+ goto out;
+ }
+
+ active = get_active_aaprofile();
+ if (!active) {
+ char *profile = cmd + strlen(cmd_setprofile);
+ size_t profilesize = size - strlen(cmd_setprofile);
+
+ error = aa_setprocattr_setprofile(p, profile, profilesize);
+ if (error == 0)
+ /* success,
+ * set return to #bytes in orig request
+ */
+ error = size;
+ } else {
+ AA_WARN("%s: Attempt by confined task %s(%d) "
+ "[user %d] to assign profile to task %s(%d)\n",
+ __FUNCTION__,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+
+ error = -EACCES;
+ }
+ put_aaprofile(active);
+ } else {
+ /* unknown operation */
+ AA_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) "
+ "[user %d] for task %s(%d)\n",
+ __FUNCTION__,
+ size < 16 ? (int)size : 16,
+ cmd,
+ current->comm,
+ current->pid,
+ current->uid,
+ p->comm,
+ p->pid);
+
+ error = -EINVAL;
+ }
+
+out:
+ return error;
+}
+
+struct security_operations apparmor_ops = {
+ .ptrace = apparmor_ptrace,
+ .capget = apparmor_capget,
+ .capset_check = apparmor_capset_check,
+ .capset_set = apparmor_capset_set,
+ .sysctl = apparmor_sysctl,
+ .capable = apparmor_capable,
+ .syslog = apparmor_syslog,
+
+ .netlink_send = apparmor_netlink_send,
+ .netlink_recv = apparmor_netlink_recv,
+
+ .bprm_apply_creds = apparmor_bprm_apply_creds,
+ .bprm_set_security = apparmor_bprm_set_security,
+
+ .sb_mount = apparmor_sb_mount,
+ .sb_umount = apparmor_umount,
+
+ .inode_mkdir = apparmor_inode_mkdir,
+ .inode_rmdir = apparmor_inode_rmdir,
+ .inode_create = apparmor_inode_create,
+ .inode_link = apparmor_inode_link,
+ .inode_unlink = apparmor_inode_unlink,
+ .inode_mknod = apparmor_inode_mknod,
+ .inode_rename = apparmor_inode_rename,
+ .inode_permission = apparmor_inode_permission,
+ .inode_setattr = apparmor_inode_setattr,
+ .inode_setxattr = apparmor_inode_setxattr,
+ .inode_getxattr = apparmor_inode_getxattr,
+ .inode_listxattr = apparmor_inode_listxattr,
+ .inode_removexattr = apparmor_inode_removexattr,
+ .file_permission = apparmor_file_permission,
+ .file_alloc_security = apparmor_file_alloc_security,
+ .file_free_security = apparmor_file_free_security,
+ .file_mmap = apparmor_file_mmap,
+
+ .task_alloc_security = apparmor_task_alloc_security,
+ .task_free_security = apparmor_task_free_security,
+ .task_post_setuid = apparmor_task_post_setuid,
+ .task_reparent_to_init = apparmor_task_reparent_to_init,
+
+ .getprocattr = apparmor_getprocattr,
+ .setprocattr = apparmor_setprocattr,
+};
+
+static int __init apparmor_init(void)
+{
+ int error;
+ const char *complainmsg = ": complainmode enabled";
+
+ if ((error = create_apparmorfs())) {
+ AA_ERROR("Unable to activate AppArmor filesystem\n");
+ goto createfs_out;
+ }
+
+ if ((error = alloc_null_complain_profile())){
+ AA_ERROR("Unable to allocate null complain profile\n");
+ goto alloc_out;
+ }
+
+ if ((error = register_security(&apparmor_ops))) {
+ AA_ERROR("Unable to load AppArmor\n");
+ goto register_security_out;
+ }
+
+ AA_INFO("AppArmor initialized%s\n",
+ apparmor_complain ? complainmsg : "");
+ aa_audit_message(NULL, GFP_KERNEL, 0,
+ "AppArmor initialized%s\n",
+ apparmor_complain ? complainmsg : "");
+
+ return error;
+
+register_security_out:
+ free_null_complain_profile();
+
+alloc_out:
+ (void)destroy_apparmorfs();
+
+createfs_out:
+ return error;
+
+}
+
+static int apparmor_exit_removeall_iter(struct subdomain *sd, void *cookie)
+{
+ /* spin_lock(&sd_lock) held here */
+
+ if (__aa_is_confined(sd)) {
+ AA_DEBUG("%s: Dropping profiles %s(%d) "
+ "profile %s(%p) active %s(%p)\n",
+ __FUNCTION__,
+ sd->task->comm, sd->task->pid,
+ BASE_PROFILE(sd->active)->name,
+ BASE_PROFILE(sd->active),
+ sd->active->name, sd->active);
+ aa_switch_unconfined(sd);
+ }
+
+ return 0;
+}
+
+static void __exit apparmor_exit(void)
+{
+ unsigned long flags;
+
+ /* Remove profiles from the global profile list.
+ * This is just for tidyness as there is no way to reference this
+ * list once the AppArmor lsm hooks are detached (below)
+ */
+ aa_profilelist_release();
+
+ /* Remove profiles from active tasks
+ * If this is not done, if module is reloaded after being removed,
+ * old profiles (still refcounted in memory) will become 'magically'
+ * reattached
+ */
+
+ spin_lock_irqsave(&sd_lock, flags);
+ aa_subdomainlist_iterate(apparmor_exit_removeall_iter, NULL);
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ /* Free up list of active subdomain */
+ aa_subdomainlist_release();
+
+ free_null_complain_profile();
+
+ destroy_apparmorfs();
+
+ if (unregister_security(&apparmor_ops))
+ AA_WARN("Unable to properly unregister AppArmor\n");
+
+ /* delay for an rcu cycle to make ensure that profiles pending
+ * destruction in the rcu callback are freed.
+ */
+ synchronize_rcu();
+
+ AA_INFO("AppArmor protection removed\n");
+ aa_audit_message(NULL, GFP_KERNEL, 0,
+ "AppArmor protection removed\n");
+}
+
+security_initcall(apparmor_init);
+module_exit(apparmor_exit);
+
+MODULE_DESCRIPTION("AppArmor process confinement");
+MODULE_AUTHOR("Tony Jones <***@suse.de>");
+MODULE_LICENSE("GPL");
Arjan van de Ven
2006-04-19 18:05:02 UTC
Permalink
Post by Tony Jones
+#ifndef MODULE
+static int __init aa_getopt_complain(char *str)
+{
+ get_option(&str, &apparmor_complain);
+ return 1;
+}
+__setup("apparmor_complain=", aa_getopt_complain);
this is just bogus; in 2.6 at least. No need for ifdef; module
parameters can be set on the kernel commandline for the non-module case



-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:50:10 UTC
Permalink
The file match.h specifies a sub module interface consisting of the following
functions:

aamatch_alloc
aamatch_free
Allocates/deallocates submodule specific data used by each
loaded profile (policy).

aamatch_features
Returns the list of features implemented by the submodule.
These are literal, tailglob ("/path/**" match all paths
below /path) and pattern (full shell based pathname expansion).

aamatch_serialize
Called by the module interface to serialize submodule specific
data from userspace.

aamatch_match
Called to perform matching on a generated pathname.


The submodule submitted here implements only "literal" and "tailglob".
The version included with SuSE Linux implements "pattern" but via a method
that is not acceptable for mainline inclusion. We plan on developing
a new submodule as soon as possible that will implement the missing
functionality of the SuSE release using the textsearch framework and
a new bounded textsearch algorithm acceptable for subsequent inclusion
into the mainline kernel.


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/match/Makefile | 5 +
security/apparmor/match/match.h | 132 ++++++++++++++++++++++++++++++++
security/apparmor/match/match_default.c | 57 +++++++++++++
3 files changed, 194 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/match/Makefile
@@ -0,0 +1,5 @@
+# Makefile for AppArmor aamatch submodule
+#
+obj-$(CONFIG_SECURITY_APPARMOR) += aamatch_default.o
+
+aamatch_default-y := match_default.o
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/match/match.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2002-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor submodule (match) prototypes
+ */
+
+#ifndef __MATCH_H
+#define __MATCH_H
+
+#include "../module_interface.h"
+#include "../apparmor.h"
+
+/* The following functions implement an interface used by the primary
+ * AppArmor module to perform name matching (n.b. "AppArmor" was previously
+ * called "SubDomain").
+
+ * aamatch_alloc
+ * aamatch_free
+ * aamatch_features
+ * aamatch_serialize
+ * aamatch_match
+ *
+ * The intent is for the primary module to export (via virtual fs entries)
+ * the features provided by the submodule (aamatch_features) so that the
+ * parser may only load policy that can be supported.
+ *
+ * The primary module will call aamatch_serialize to allow the submodule
+ * to consume submodule specific data from parser data stream and will call
+ * aamatch_match to determine if a pathname matches an aa_entry.
+ */
+
+typedef int (*aamatch_serializecb)
+ (struct aa_ext *, enum aa_code, void *, const char *);
+
+/**
+ * aamatch_alloc: allocate extradata (if necessary)
+ * @type: type of entry being allocated
+ * Return value: NULL indicates no data was allocated (ERR_PTR(x) on error)
+ */
+extern void* aamatch_alloc(enum entry_match_type type);
+
+/**
+ * aamatch_free: release data allocated by aamatch_alloc
+ * @entry_extradata: data previously allocated by aamatch_alloc
+ */
+extern void aamatch_free(void *entry_extradata);
+
+/**
+ * aamatch_features: return match types supported
+ * Return value: space seperated string (of types supported - use type=value
+ * to indicate variants of a type)
+ */
+extern const char* aamatch_features(void);
+
+/**
+ * aamatch_serialize: serialize extradata
+ * @entry_extradata: data previously allocated by aamatch_alloc
+ * @e: input stream
+ * @cb: callback fn (consume incoming data stream)
+ * Return value: 0 success, -ve error
+ */
+extern int aamatch_serialize(void *entry_extradata, struct aa_ext *e,
+ aamatch_serializecb cb);
+
+/**
+ * aamatch_match: determine if pathname matches entry
+ * @pathname: pathname to verify
+ * @entry_name: entry name
+ * @type: type of entry
+ * @entry_extradata: data previously allocated by aamatch_alloc
+ * Return value: 1 match, 0 othersise
+ */
+extern unsigned int aamatch_match(const char *pathname, const char *entry_name,
+ enum entry_match_type type,
+ void *entry_extradata);
+
+
+/**
+ * sd_getmatch_type - return string representation of entry_match_type
+ * @type: entry match type
+ */
+static inline const char *sd_getmatch_type(enum entry_match_type type)
+{
+ const char *names[] = {
+ "aa_entry_literal",
+ "aa_entry_tailglob",
+ "aa_entry_pattern",
+ "aa_entry_invalid"
+ };
+
+ if (type >= aa_entry_invalid) {
+ type = aa_entry_invalid;
+ }
+
+ return names[type];
+}
+
+/**
+ * aamatch_match_common - helper function to check if a pathname matches
+ * a literal/tailglob
+ * @path: path requested to search for
+ * @entry_name: name from aa_entry
+ * @type: type of entry
+ */
+static inline int aamatch_match_common(const char *path,
+ const char *entry_name,
+ enum entry_match_type type)
+{
+ int retval;
+
+ /* literal, no pattern matching characters */
+ if (type == aa_entry_literal) {
+ retval = (strcmp(entry_name, path) == 0);
+ /* trailing ** glob pattern */
+ } else if (type == aa_entry_tailglob) {
+ retval = (strncmp(entry_name, path,
+ strlen(entry_name) - 2) == 0);
+ } else {
+ AA_WARN("%s: Invalid entry_match_type %d\n",
+ __FUNCTION__, type);
+ retval = 0;
+ }
+
+ return retval;
+}
+
+#endif /* __MATCH_H */
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/match/match_default.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2002-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * http://forge.novell.com/modules/xfmod/project/?apparmor
+ *
+ * AppArmor default match submodule (literal and tailglob)
+ */
+
+#include <linux/module.h>
+#include "match.h"
+
+static const char *features="literal tailglob";
+
+void* aamatch_alloc(enum entry_match_type type)
+{
+ return NULL;
+}
+
+void aamatch_free(void *ptr)
+{
+}
+
+const char *aamatch_features(void)
+{
+ return features;
+}
+
+int aamatch_serialize(void *entry_extradata, struct aa_ext *e,
+ aamatch_serializecb cb)
+{
+ return 0;
+}
+
+unsigned int aamatch_match(const char *pathname, const char *entry_name,
+ enum entry_match_type type, void *entry_extradata)
+{
+ int ret;
+
+ ret = aamatch_match_common(pathname, entry_name, type);
+
+ return ret;
+}
+
+EXPORT_SYMBOL_GPL(aamatch_alloc);
+EXPORT_SYMBOL_GPL(aamatch_free);
+EXPORT_SYMBOL_GPL(aamatch_features);
+EXPORT_SYMBOL_GPL(aamatch_serialize);
+EXPORT_SYMBOL_GPL(aamatch_match);
+
+MODULE_DESCRIPTION("AppArmor match module (aamatch) [default]");
+MODULE_AUTHOR("Tony Jones <***@suse.de>");
+MODULE_LICENSE("GPL");
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-19 17:49:54 UTC
Permalink
This patch implements the interface between the userspace policy loader
and the kernel module. It is called by the .load, .remove and .replace
file_operations hooks implemented in apparmorfs.c.

The code is reponsible for serializing data in a platform independant
manner from userspace and creating/activating the necessary apparmor
profiles.

Certain aspects are delegated to the sub matching module which implements
the aamatch_* functions.


Signed-off-by: Tony Jones <***@suse.de>

---
security/apparmor/module_interface.c | 840 +++++++++++++++++++++++++++++++++++
security/apparmor/module_interface.h | 37 +
2 files changed, 877 insertions(+)

--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/module_interface.c
@@ -0,0 +1,840 @@
+/*
+ * Copyright (C) 1998-2005 Novell/SUSE
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * AppArmor userspace policy interface
+ */
+
+#include <asm/unaligned.h>
+
+#include "apparmor.h"
+#include "inline.h"
+#include "module_interface.h"
+#include "match/match.h"
+
+/* aa_code defined in module_interface.h */
+
+const int aacode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 };
+
+struct aa_taskreplace_data {
+ struct aaprofile *old_profile;
+ struct aaprofile *new_profile;
+};
+
+/* inlines must be forward of there use in newer version of gcc,
+ just forward declaring with a prototype won't work anymore */
+
+static inline void free_aa_entry(struct aa_entry *entry)
+{
+ if (entry) {
+ kfree(entry->filename);
+ aamatch_free(entry->extradata);
+ kfree(entry);
+ }
+}
+
+/**
+ * alloc_aa_entry - create new empty aa_entry
+ * This routine allocates, initializes, and returns a new aa_entry
+ * file entry structure. Structure is zeroed. Returns new structure on
+ * success, %NULL on failure.
+ */
+static inline struct aa_entry *alloc_aa_entry(void)
+{
+ struct aa_entry *entry;
+
+ AA_DEBUG("%s\n", __FUNCTION__);
+ entry = kzalloc(sizeof(struct aa_entry), GFP_KERNEL);
+ if (entry) {
+ int i;
+ INIT_LIST_HEAD(&entry->list);
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
+ INIT_LIST_HEAD(&entry->listp[i]);
+ }
+ }
+ return entry;
+}
+
+/**
+ * free_aaprofile_rcu - rcu callback for free profiles
+ * @head: rcu_head struct of the profile whose reference is being put.
+ *
+ * the rcu callback routine, which delays the freeing of a profile when
+ * its last reference is put.
+ */
+static void free_aaprofile_rcu(struct rcu_head *head)
+{
+ struct aaprofile *p = container_of(head, struct aaprofile, rcu);
+ free_aaprofile(p);
+}
+
+/**
+ * task_remove - remove profile from a task's subdomain
+ * @sd: task's subdomain
+ *
+ * remove the active profile from a task's subdomain, switching the task
+ * to an unconfined state.
+ */
+static inline void task_remove(struct subdomain *sd)
+{
+ /* spin_lock(&sd_lock) held here */
+ AA_DEBUG("%s: removing profile from task %s(%d) profile %s active %s\n",
+ __FUNCTION__,
+ sd->task->comm,
+ sd->task->pid,
+ BASE_PROFILE(sd->active)->name,
+ sd->active->name);
+
+ aa_switch_unconfined(sd);
+}
+
+/** taskremove_iter - Iterator to unconfine subdomains which match cookie
+ * @sd: subdomain to consider for profile removal
+ * @cookie: pointer to the oldprofile which is being removed
+ *
+ * If the subdomain's active profile matches old_profile, then call
+ * task_remove() to remove the profile leaving the task (subdomain) unconfined.
+ */
+static int taskremove_iter(struct subdomain *sd, void *cookie)
+{
+ struct aaprofile *old_profile = (struct aaprofile *)cookie;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ if (__aa_is_confined(sd) && BASE_PROFILE(sd->active) == old_profile) {
+ task_remove(sd);
+ }
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ return 0;
+}
+
+/** task_replace - replace subdomain's current profile with a new profile
+ * @sd: subdomain to replace the profile on
+ * @new: new profile
+ *
+ * Replace a task's (subdomain's) active profile with a new profile. If
+ * task was in a hat then the new profile will also be in the equivalent
+ * hat in the new profile if it exists. If it doesn't exist the
+ * task will be placed in the special null_profile state.
+ */
+static inline void task_replace(struct subdomain *sd, struct aaprofile *new)
+{
+ AA_DEBUG("%s: replacing profile for task %s(%d) "
+ "profile=%s (%p) active=%s (%p)\n",
+ __FUNCTION__,
+ sd->task->comm, sd->task->pid,
+ BASE_PROFILE(sd->active)->name, BASE_PROFILE(sd->active),
+ sd->active->name, sd->active);
+
+ if (!sd->active)
+ goto out;
+
+ if (IN_SUBPROFILE(sd->active)) {
+ struct aaprofile *nactive;
+
+ /* The old profile was in a hat, check to see if the new
+ * profile has an equivalent hat */
+ nactive = __aa_find_profile(sd->active->name, &new->sub);
+
+ if (!nactive)
+ nactive = get_aaprofile(new->null_profile);
+
+ aa_switch(sd, nactive);
+ put_aaprofile(nactive);
+ } else {
+ aa_switch(sd, new);
+ }
+
+ out:
+ return;
+}
+
+/** taskreplace_iter - Iterator to replace a subdomain's profile
+ * @sd: subdomain to consider for profile replacement
+ * @cookie: pointer to the old profile which is being replaced.
+ *
+ * If the subdomain's active profile matches old_profile call
+ * task_replace() to replace with the subdomain's active profile with
+ * the new profile.
+ */
+static int taskreplace_iter(struct subdomain *sd, void *cookie)
+{
+ struct aa_taskreplace_data *data = (struct aa_taskreplace_data *)cookie;
+ unsigned long flags;
+
+ spin_lock_irqsave(&sd_lock, flags);
+
+ if (__aa_is_confined(sd) &&
+ BASE_PROFILE(sd->active) == data->old_profile)
+ task_replace(sd, data->new_profile);
+
+ spin_unlock_irqrestore(&sd_lock, flags);
+
+ return 0;
+}
+
+static inline int aa_inbounds(struct aa_ext *e, size_t size)
+{
+ return (e->pos + size <= e->end);
+}
+
+/**
+ * aaconvert - convert trailing values of serialized type codes
+ * @code: type code
+ * @dest: pointer to object to receive the converted value
+ * @src: pointer to value to convert
+ *
+ * for serialized type codes which have a trailing value, convert it
+ * and place it in @dest. If a code does not have a trailing value nop.
+ */
+static void aaconvert(enum aa_code code, void *dest, void *src)
+{
+ switch (code) {
+ case AA_U8:
+ *(u8 *)dest = *(u8 *) src;
+ break;
+ case AA_U16:
+ case AA_NAME:
+ case AA_DYN_STRING:
+ *(u16 *)dest = le16_to_cpu(get_unaligned((u16 *)src));
+ break;
+ case AA_U32:
+ case AA_STATIC_BLOB:
+ *(u32 *)dest = le32_to_cpu(get_unaligned((u32 *)src));
+ break;
+ case AA_U64:
+ *(u64 *)dest = le64_to_cpu(get_unaligned((u64 *)src));
+ break;
+ default:
+ /* nop - all other type codes do not have a trailing value */
+ ;
+ }
+}
+
+/**
+ * aa_is_X - check if the next element is of type X
+ * @e: serialized data extent information
+ * @code: type code
+ * @data: object located at @e->pos (of type @code) is written into @data
+ * if @data is non-null. if data is null it means skip this
+ * entry
+ * check to see if the next element in the serialized data stream is of type
+ * X and check that it is with in bounds, if so put the associated value in
+ * @data.
+ * return the size of bytes associated with the returned data
+ * for complex object like blob and string a pointer to the allocated
+ * data is returned in data, but the size of the blob or string is
+ * returned.
+ */
+static u32 aa_is_X(struct aa_ext *e, enum aa_code code, void *data)
+{
+ void *pos = e->pos;
+ int ret = 0;
+ if (!aa_inbounds(e, AA_CODE_BYTE + aacode_datasize[code]))
+ goto fail;
+ if (code != *(u8 *)e->pos)
+ goto out;
+ e->pos += AA_CODE_BYTE;
+ if (code == AA_NAME) {
+ u16 size;
+ /* name codes are followed by X bytes */
+ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data)
+ *(u16 *)data = size;
+ e->pos += aacode_datasize[code];
+ ret = 1 + aacode_datasize[code];
+ } else if (code == AA_DYN_STRING) {
+ u16 size;
+ char *str;
+ /* strings codes are followed by X bytes */
+ size = le16_to_cpu(get_unaligned((u16 *)e->pos));
+ e->pos += aacode_datasize[code];
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data) {
+ * (char **)data = NULL;
+ str = kmalloc(size, GFP_KERNEL);
+ if (!str)
+ goto fail;
+ memcpy(str, e->pos, (size_t) size);
+ str[size-1] = '\0';
+ * (char **)data = str;
+ }
+ e->pos += size;
+ ret = size;
+ } else if (code == AA_STATIC_BLOB) {
+ u32 size;
+ /* blobs are followed by X bytes, that can be 2^32 */
+ size = le32_to_cpu(get_unaligned((u32 *)e->pos));
+ e->pos += aacode_datasize[code];
+ if (!aa_inbounds(e, (size_t) size))
+ goto fail;
+ if (data)
+ memcpy(data, e->pos, (size_t) size);
+ e->pos += size;
+ ret = size;
+ } else {
+ if (data)
+ aaconvert(code, data, e->pos);
+ e->pos += aacode_datasize[code];
+ ret = 1 + aacode_datasize[code];
+ }
+out:
+ return ret;
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/**
+ * aa_is_nameX - check is the next element is of type X with a name of @name
+ * @e: serialized data extent information
+ * @code: type code
+ * @data: location to store deserialized data if match isX criteria
+ * @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 the code matches and name (if specified) matches then
+ * the packed data is unpacked into *data. (Note for strings this is the
+ * size, and the next data in the stream is the string data)
+ * returns %0 if either match failes
+ */
+static int aa_is_nameX(struct aa_ext *e, enum aa_code code, void *data,
+ const char *name)
+{
+ void *pos = e->pos;
+ u16 size;
+ u32 ret;
+ /* check for presence of a tagname, and if present name size
+ * AA_NAME tag value is a u16 */
+ if (aa_is_X(e, AA_NAME, &size)) {
+ /* if a name is specified it must match. otherwise skip tag */
+ if (name && ((strlen(name) != size-1) ||
+ strncmp(name, (char *)e->pos, (size_t)size-1)))
+ goto fail;
+ e->pos += size;
+ }
+ /* now check if data actually matches */
+ ret = aa_is_X(e, code, data);
+ if (!ret)
+ goto fail;
+ return ret;
+
+fail:
+ e->pos = pos;
+ return 0;
+}
+
+/* macro to wrap error case to make a block of reads look nicer */
+#define AA_READ_X(E, C, D, N) \
+ do { \
+ u32 __ret; \
+ __ret = aa_is_nameX((E), (C), (D), (N)); \
+ if (!__ret) \
+ goto fail; \
+ } while (0)
+
+/**
+ * aa_activate_net_entry - unpacked serialized net entries
+ * @e: serialized data extent information
+ *
+ * Ignore/skips net entries if they are present in the serialized data
+ * stream. Network confinement rules are currently unsupported but some
+ * user side tools can generate them so they are currently ignored.
+ */
+static inline int aa_activate_net_entry(struct aa_ext *e)
+{
+ AA_READ_X(e, AA_STRUCT, NULL, "ne");
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U32, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ AA_READ_X(e, AA_U16, NULL, NULL);
+ /* interface name is optional so just ignore return code */
+ aa_is_nameX(e, AA_DYN_STRING, NULL, NULL);
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return 1;
+fail:
+ return 0;
+}
+
+/**
+ * aa_activate_file_entry - unpack serialized file entry
+ * @e: serialized data extent information
+ *
+ * unpack the information used for a file ACL entry.
+ */
+static inline struct aa_entry *aa_activate_file_entry(struct aa_ext *e)
+{
+ struct aa_entry *entry = NULL;
+
+ if (!(entry = alloc_aa_entry()))
+ goto fail;
+
+ AA_READ_X(e, AA_STRUCT, NULL, "fe");
+ AA_READ_X(e, AA_DYN_STRING, &entry->filename, NULL);
+ AA_READ_X(e, AA_U32, &entry->mode, "file.mode");
+ AA_READ_X(e, AA_U32, &entry->type, "file.pattern_type");
+
+ entry->extradata = aamatch_alloc(entry->type);
+ if (IS_ERR(entry->extradata)) {
+ entry->extradata = NULL;
+ goto fail;
+ }
+
+ if (entry->extradata &&
+ aamatch_serialize(entry->extradata, e, aa_is_nameX) != 0) {
+ goto fail;
+ }
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ switch (entry->type) {
+ case aa_entry_literal:
+ AA_DEBUG("%s: %s [no pattern] mode=0x%x\n",
+ __FUNCTION__,
+ entry->filename,
+ entry->mode);
+ break;
+ case aa_entry_tailglob:
+ AA_DEBUG("%s: %s [tailglob] mode=0x%x\n",
+ __FUNCTION__,
+ entry->filename,
+ entry->mode);
+ break;
+ case aa_entry_pattern:
+ AA_DEBUG("%s: %s mode=0x%x\n",
+ __FUNCTION__,
+ entry->filename,
+ entry->mode);
+ break;
+ default:
+ AA_WARN("%s: INVALID entry_match_type %d\n",
+ __FUNCTION__,
+ (int)entry->type);
+ goto fail;
+ }
+
+ return entry;
+
+fail:
+ aamatch_free(entry->extradata);
+ free_aa_entry(entry);
+ return NULL;
+}
+
+/**
+ * check_rule_and_add - check a file rule is valid and add to a profile
+ * @file_entry: file rule to add
+ * @profile: profile to add the rule to
+ * @message: error message returned if the addition failes.
+ *
+ * perform consistency check to ensure that a file rule entry is valid.
+ * If the rule is valid it is added to the profile.
+ */
+static inline int check_rule_and_add(struct aa_entry *file_entry,
+ struct aaprofile *profile,
+ const char **message)
+{
+ /* verify consistency of x, px, ix, ux for entry against
+ possible duplicates for this entry */
+ int mode = AA_EXEC_MODIFIER_MASK(file_entry->mode);
+ int i;
+
+ if (mode && !(AA_MAY_EXEC & file_entry->mode)) {
+ *message = "inconsistent rule, x modifiers without x";
+ goto out;
+ }
+
+ /* check that only 1 of the modifiers is set */
+ if (mode && (mode & (mode - 1))) {
+ *message = "inconsistent rule, multiple x modifiers";
+ goto out;
+ }
+
+ list_add(&file_entry->list, &profile->file_entry);
+ profile->num_file_entries++;
+
+ mode = file_entry->mode;
+
+ /* Handle partitioned lists
+ * Chain entries onto sublists based on individual
+ * permission bits. This allows more rapid searching.
+ */
+ for (i = 0; i <= POS_AA_FILE_MAX; i++) {
+ if (mode & (1 << i))
+ /* profile->file_entryp[i] initially set to
+ * NULL in alloc_aaprofile() */
+ list_add(&file_entry->listp[i],
+ &profile->file_entryp[i]);
+ }
+
+ return 1;
+
+out:
+ free_aa_entry(file_entry);
+ return 0;
+}
+
+#define AA_ENTRY_LIST(NAME) \
+ do { \
+ if (aa_is_nameX(e, AA_LIST, NULL, (NAME))) { \
+ rulename = ""; \
+ error_string = "Invalid file entry"; \
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) { \
+ struct aa_entry *file_entry; \
+ file_entry = aa_activate_file_entry(e); \
+ if (!file_entry) \
+ goto fail; \
+ if (!check_rule_and_add(file_entry, profile, \
+ &error_string)) { \
+ rulename = file_entry->filename; \
+ goto fail; \
+ } \
+ } \
+ } \
+ } while (0)
+
+/**
+ * aa_activate_profile - unpack a serialized profile
+ * @e: serialized data extent information
+ * @error: error code returned if unpacking fails
+ */
+static struct aaprofile *aa_activate_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aaprofile *profile = NULL;
+ const char *rulename = "";
+ const char *error_string = "Invalid Profile";
+
+ *error = -EPROTO;
+
+ profile = alloc_aaprofile();
+ if (!profile) {
+ error_string = "Could not allocate profile";
+ *error = -ENOMEM;
+ goto fail;
+ }
+
+ /* check that we have the right struct being passed */
+ AA_READ_X(e, AA_STRUCT, NULL, "profile");
+ AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL);
+
+ error_string = "Invalid flags";
+ /* per profile debug flags (debug, complain, audit) */
+ AA_READ_X(e, AA_STRUCT, NULL, "flags");
+ AA_READ_X(e, AA_U32, &(profile->flags.debug), "profile.flags.debug");
+ AA_READ_X(e, AA_U32, &(profile->flags.complain),
+ "profile.flags.complain");
+ AA_READ_X(e, AA_U32, &(profile->flags.audit), "profile.flags.audit");
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ error_string = "Invalid capabilities";
+ AA_READ_X(e, AA_U32, &(profile->capabilities), "profile.capabilities");
+
+ /* get the file entries. */
+ AA_ENTRY_LIST("pgent"); /* pcre rules */
+ AA_ENTRY_LIST("sgent"); /* simple globs */
+ AA_ENTRY_LIST("fent"); /* regular file entries */
+
+ /* get the net entries */
+ if (aa_is_nameX(e, AA_LIST, NULL, "net")) {
+ error_string = "Invalid net entry";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ if (!aa_activate_net_entry(e))
+ goto fail;
+ }
+ }
+ rulename = "";
+
+ /* get subprofiles */
+ if (aa_is_nameX(e, AA_LIST, NULL, "hats")) {
+ error_string = "Invalid profile hat";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ struct aaprofile *subprofile;
+ subprofile = aa_activate_profile(e, error);
+ if (!subprofile)
+ goto fail;
+ subprofile->parent = profile;
+ list_add(&subprofile->list, &profile->sub);
+ }
+ }
+
+ error_string = "Invalid end of profile";
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return profile;
+
+fail:
+ AA_WARN("%s: %s %s in profile %s\n", INTERFACE_ID, rulename,
+ error_string, profile && profile->name ? profile->name
+ : "unknown");
+
+ if (profile) {
+ free_aaprofile(profile);
+ profile = NULL;
+ }
+
+ return NULL;
+}
+
+/**
+ * aa_activate_top_profile - unpack a serialized base profile
+ * @e: serialized data extent information
+ * @error: error code returned if unpacking fails
+ *
+ * check interface version unpack a profile and all its hats and patch
+ * in any extra information that the profile needs.
+ */
+static void *aa_activate_top_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aaprofile *profile = NULL;
+
+ /* get the interface version */
+ if (!aa_is_nameX(e, AA_U32, &e->version, "version")) {
+ AA_WARN("%s: version missing\n", INTERFACE_ID);
+ *error = -EPROTONOSUPPORT;
+ goto fail;
+ }
+
+ /* check that the interface version is currently supported */
+ if (e->version != 2) {
+ AA_WARN("%s: unsupported interface version (%d)\n",
+ INTERFACE_ID, e->version);
+ *error = -EPROTONOSUPPORT;
+ goto fail;
+ }
+
+ profile = aa_activate_profile(e, error);
+ if (!profile)
+ goto fail;
+
+ if (!list_empty(&profile->sub) || profile->flags.complain) {
+ if (attach_nullprofile(profile))
+ goto fail;
+ }
+ return profile;
+
+fail:
+ free_aaprofile(profile);
+ return NULL;
+}
+
+/**
+ * aa_file_prof_add - add a new profile to the profile list
+ * @data: serialized data stream
+ * @size: size of the serialized data stream
+ *
+ * unpack and add a profile to the profile list. Return %0 or error
+ */
+ssize_t aa_file_prof_add(void *data, size_t size)
+{
+ struct aaprofile *profile = NULL;
+
+ struct aa_ext e = {
+ .start = data,
+ .end = data + size,
+ .pos = data
+ };
+ ssize_t error;
+
+ profile = aa_activate_top_profile(&e, &error);
+ if (!profile) {
+ AA_DEBUG("couldn't activate profile\n");
+ goto out;
+ }
+
+ /* aa_activate_top_profile allocates profile with initial 1 count
+ * aa_profilelist_add transfers that ref to profile list without
+ * further incrementing
+ */
+ if (aa_profilelist_add(profile)) {
+ error = size;
+ } else {
+ AA_WARN("trying to add profile (%s) that already exists.\n",
+ profile->name);
+ put_aaprofile(profile);
+ error = -EEXIST;
+ }
+
+out:
+ return error;
+}
+
+/**
+ * aa_file_prof_repl - replace a profile 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 subdomain. If the profile does not exist on the profile list
+ * it is added. Return %0 or error.
+ */
+ssize_t aa_file_prof_repl(void *udata, size_t size)
+{
+ struct aa_taskreplace_data data;
+ struct aa_ext e = {
+ .start = udata,
+ .end = udata + size,
+ .pos = udata
+ };
+
+ ssize_t error;
+
+ data.new_profile = aa_activate_top_profile(&e, &error);
+ if (!data.new_profile) {
+ AA_DEBUG("couldn't activate profile\n");
+ goto out;
+ }
+
+ /* Refcount on data.new_profile is 1 (aa_activate_top_profile).
+ *
+ * This reference will be inherited by aa_profilelist_replace for it's
+ * profile list reference but this isn't sufficient.
+ *
+ * Another replace (*for-same-profile*) may race us here.
+ * Task A calls aa_profilelist_replace(new_profile) and is interrupted.
+ * Task B old_profile = aa_profilelist_replace() will return task A's
+ * new_profile with the count of 1. If task B proceeeds to put this
+ * profile it will dissapear from under task A.
+ *
+ * Grab extra reference on new_profile to prevent this
+ */
+
+ get_aaprofile(data.new_profile);
+
+ data.old_profile = aa_profilelist_replace(data.new_profile);
+
+ /* If there was an old profile, find all currently executing tasks
+ * using this profile and replace the old profile with the new.
+ */
+ if (data.old_profile) {
+ AA_DEBUG("%s: try to replace profile (%p)%s\n",
+ __FUNCTION__,
+ data.old_profile,
+ data.old_profile->name);
+
+ aa_subdomainlist_iterate(taskreplace_iter, (void *)&data);
+
+ /* it's off global list, and we are done replacing */
+ put_aaprofile(data.old_profile);
+ }
+
+ /* release extra reference obtained above (race) */
+ put_aaprofile(data.new_profile);
+
+ error = size;
+
+out:
+ return error;
+}
+
+/**
+ * aa_file_prof_remove - remove a profile from the system
+ * @name: name of the profile to remove
+ * @size: size of the name
+ *
+ * remove a profile from the profile list and all subdomain references
+ * to said profile. Return %0 on success, else error.
+ */
+ssize_t aa_file_prof_remove(const char *name, size_t size)
+{
+ struct aaprofile *old_profile;
+
+ /* if the old profile exists it will be removed from the list and
+ * a reference is returned.
+ */
+ old_profile = aa_profilelist_remove(name);
+
+ if (old_profile) {
+ /* remove profile from any tasks using it */
+ aa_subdomainlist_iterate(taskremove_iter, (void *)old_profile);
+
+ /* drop reference obtained by aa_profilelist_remove */
+ put_aaprofile(old_profile);
+ } else {
+ AA_WARN("%s: trying to remove profile (%s) that "
+ "doesn't exist - skipping.\n", __FUNCTION__, name);
+ return -ENOENT;
+ }
+
+ return size;
+}
+
+/**
+ * free_aaprofile_kref - free aaprofile by kref (called by put_aaprofile)
+ * @kr: kref callback for freeing of a profile
+ */
+void free_aaprofile_kref(struct kref *kr)
+{
+ struct aaprofile *p=container_of(kr, struct aaprofile, count);
+
+ call_rcu(&p->rcu, free_aaprofile_rcu);
+}
+
+/**
+ * free_aaprofile - free aaprofile structure
+ * @profile: the profile to free
+ *
+ * free a profile, its file entries hats and null_profile. All references
+ * to the profile, its hats and null_profile must have been put.
+ * If the profile was referenced by a subdomain free_aaprofile should be
+ * called from an rcu callback routine.
+ */
+void free_aaprofile(struct aaprofile *profile)
+{
+ struct aa_entry *ent, *tmp;
+ struct aaprofile *p, *ptmp;
+
+ AA_DEBUG("%s(%p)\n", __FUNCTION__, profile);
+
+ if (!profile)
+ return;
+
+ /* profile is still on global profile list -- invalid */
+ if (!list_empty(&profile->list)) {
+ AA_ERROR("%s: internal error, "
+ "profile '%s' still on global list\n",
+ __FUNCTION__,
+ profile->name);
+ BUG();
+ }
+
+ list_for_each_entry_safe(ent, tmp, &profile->file_entry, list) {
+ if (ent->filename)
+ AA_DEBUG("freeing aa_entry: %p %s\n",
+ ent->filename, ent->filename);
+ list_del_init(&ent->list);
+ free_aa_entry(ent);
+ }
+
+ /* use free_aaprofile instead of put_aaprofile to destroy the
+ * null_profile, because the null_profile use the same reference
+ * counting as hats, ie. the count goes to the base profile.
+ */
+ free_aaprofile(profile->null_profile);
+ list_for_each_entry_safe(p, ptmp, &profile->sub, list) {
+ list_del_init(&p->list);
+ p->parent = NULL;
+ put_aaprofile(p);
+ }
+
+ if (profile->name) {
+ AA_DEBUG("%s: %s\n", __FUNCTION__, profile->name);
+ kfree(profile->name);
+ }
+
+ kfree(profile);
+}
--- /dev/null
+++ linux-2.6.17-rc1/security/apparmor/module_interface.h
@@ -0,0 +1,37 @@
+#ifndef __MODULEINTERFACE_H
+#define __MODULEINTERFACE_H
+
+/* Codes of the types of basic structures that are understood */
+#define AA_CODE_BYTE (sizeof(u8))
+#define INTERFACE_ID "INTERFACE"
+
+#define SUBDOMAIN_INTERFACE_VERSION 2
+
+enum aa_code {
+ AA_U8,
+ AA_U16,
+ AA_U32,
+ AA_U64,
+ AA_NAME, /* same as string except it is items name */
+ AA_DYN_STRING,
+ AA_STATIC_BLOB,
+ AA_STRUCT,
+ AA_STRUCTEND,
+ AA_LIST,
+ AA_LISTEND,
+ AA_OFFSET,
+ AA_BAD
+};
+
+/* aa_ext tracks the kernel buffer and read position in it. The interface
+ * data is copied into a kernel buffer in apparmorfs and then handed off to
+ * the activate routines.
+ */
+struct aa_ext {
+ void *start;
+ void *end;
+ void *pos; /* pointer to current position in the buffer */
+ u32 version;
+};
+
+#endif /* __MODULEINTERFACE_H */
Tony Jones
2006-04-21 18:01:15 UTC
Permalink
Hi!
Post by Tony Jones
This patch implements the interface between the userspace policy loader
and the kernel module. It is called by the .load, .remove and .replace
file_operations hooks implemented in apparmorfs.c.
The code is reponsible for serializing data in a platform independant
manner from userspace and creating/activating the necessary apparmor
profiles.
Documentation patch describing what kind of data you pass here would
be nice.
Very true. We will add it.
Post by Tony Jones
+#include "match/match.h"
+
+/* aa_code defined in module_interface.h */
+
+const int aacode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 };
I believe this needs a comment.
Yep.
Post by Tony Jones
+
+/* inlines must be forward of there use in newer version of gcc,
+ just forward declaring with a prototype won't work anymore */
their use?
Thanks
Post by Tony Jones
+/**
+ * aa_activate_profile - unpack a serialized profile
+ */
+static struct aaprofile *aa_activate_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aaprofile *profile = NULL;
+ const char *rulename = "";
+ const char *error_string = "Invalid Profile";
+
+ *error = -EPROTO;
+
+ profile = alloc_aaprofile();
+ if (!profile) {
+ error_string = "Could not allocate profile";
+ *error = -ENOMEM;
+ goto fail;
+ }
+
+ /* check that we have the right struct being passed */
+ AA_READ_X(e, AA_STRUCT, NULL, "profile");
+ AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL);
+
+ error_string = "Invalid flags";
+ /* per profile debug flags (debug, complain, audit) */
+ AA_READ_X(e, AA_STRUCT, NULL, "flags");
+ AA_READ_X(e, AA_U32, &(profile->flags.debug), "profile.flags.debug");
+ AA_READ_X(e, AA_U32, &(profile->flags.complain),
+ "profile.flags.complain");
+ AA_READ_X(e, AA_U32, &(profile->flags.audit), "profile.flags.audit");
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ error_string = "Invalid capabilities";
+ AA_READ_X(e, AA_U32, &(profile->capabilities), "profile.capabilities");
+
+ /* get the file entries. */
+ AA_ENTRY_LIST("pgent"); /* pcre rules */
+ AA_ENTRY_LIST("sgent"); /* simple globs */
+ AA_ENTRY_LIST("fent"); /* regular file entries */
+
+ /* get the net entries */
+ if (aa_is_nameX(e, AA_LIST, NULL, "net")) {
+ error_string = "Invalid net entry";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ if (!aa_activate_net_entry(e))
+ goto fail;
+ }
+ }
+ rulename = "";
+
+ /* get subprofiles */
+ if (aa_is_nameX(e, AA_LIST, NULL, "hats")) {
+ error_string = "Invalid profile hat";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ struct aaprofile *subprofile;
+ subprofile = aa_activate_profile(e, error);
+ if (!subprofile)
+ goto fail;
+ subprofile->parent = profile;
+ list_add(&subprofile->list, &profile->sub);
+ }
+ }
+
+ error_string = "Invalid end of profile";
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return profile;
Is this kind of transltion neccessary?
Don't understand. Please expand/clarify. The code serializes the profile data
from userspace.

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Pavel Machek
2006-04-21 18:41:49 UTC
Permalink
Hi!
Post by Tony Jones
Post by Tony Jones
+/**
+ * aa_activate_profile - unpack a serialized profile
+ */
+static struct aaprofile *aa_activate_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aaprofile *profile = NULL;
+ const char *rulename = "";
+ const char *error_string = "Invalid Profile";
+
+ *error = -EPROTO;
+
+ profile = alloc_aaprofile();
+ if (!profile) {
+ error_string = "Could not allocate profile";
+ *error = -ENOMEM;
+ goto fail;
+ }
+
+ /* check that we have the right struct being passed */
+ AA_READ_X(e, AA_STRUCT, NULL, "profile");
+ AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL);
+
+ error_string = "Invalid flags";
+ /* per profile debug flags (debug, complain, audit) */
+ AA_READ_X(e, AA_STRUCT, NULL, "flags");
+ AA_READ_X(e, AA_U32, &(profile->flags.debug), "profile.flags.debug");
+ AA_READ_X(e, AA_U32, &(profile->flags.complain),
+ "profile.flags.complain");
+ AA_READ_X(e, AA_U32, &(profile->flags.audit), "profile.flags.audit");
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ error_string = "Invalid capabilities";
+ AA_READ_X(e, AA_U32, &(profile->capabilities), "profile.capabilities");
+
+ /* get the file entries. */
+ AA_ENTRY_LIST("pgent"); /* pcre rules */
+ AA_ENTRY_LIST("sgent"); /* simple globs */
+ AA_ENTRY_LIST("fent"); /* regular file entries */
+
+ /* get the net entries */
+ if (aa_is_nameX(e, AA_LIST, NULL, "net")) {
+ error_string = "Invalid net entry";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ if (!aa_activate_net_entry(e))
+ goto fail;
+ }
+ }
+ rulename = "";
+
+ /* get subprofiles */
+ if (aa_is_nameX(e, AA_LIST, NULL, "hats")) {
+ error_string = "Invalid profile hat";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ struct aaprofile *subprofile;
+ subprofile = aa_activate_profile(e, error);
+ if (!subprofile)
+ goto fail;
+ subprofile->parent = profile;
+ list_add(&subprofile->list, &profile->sub);
+ }
+ }
+
+ error_string = "Invalid end of profile";
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return profile;
Is this kind of transltion neccessary?
Don't understand. Please expand/clarify. The code serializes the profile data
from userspace.
Yes, and it looks quite complex; would it be possible to get rid of
this serializing code? Could some existing infrastructure be used? One
file per value? Just pass the structure and make sure userspace is
matched to kernel? Something else?

Pavel
--
Thanks for all the (sleeping) penguins.
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Pavel Machek
2006-04-20 21:39:43 UTC
Permalink
Hi!
Post by Tony Jones
This patch implements the interface between the userspace policy loader
and the kernel module. It is called by the .load, .remove and .replace
file_operations hooks implemented in apparmorfs.c.
The code is reponsible for serializing data in a platform independant
manner from userspace and creating/activating the necessary apparmor
profiles.
Documentation patch describing what kind of data you pass here would
be nice.
Post by Tony Jones
+#include "match/match.h"
+
+/* aa_code defined in module_interface.h */
+
+const int aacode_datasize[] = { 1, 2, 4, 8, 2, 2, 4, 0, 0, 0, 0, 0, 0 };
I believe this needs a comment.
Post by Tony Jones
+
+/* inlines must be forward of there use in newer version of gcc,
+ just forward declaring with a prototype won't work anymore */
their use?
Post by Tony Jones
+/**
+ * aa_activate_profile - unpack a serialized profile
+ */
+static struct aaprofile *aa_activate_profile(struct aa_ext *e, ssize_t *error)
+{
+ struct aaprofile *profile = NULL;
+ const char *rulename = "";
+ const char *error_string = "Invalid Profile";
+
+ *error = -EPROTO;
+
+ profile = alloc_aaprofile();
+ if (!profile) {
+ error_string = "Could not allocate profile";
+ *error = -ENOMEM;
+ goto fail;
+ }
+
+ /* check that we have the right struct being passed */
+ AA_READ_X(e, AA_STRUCT, NULL, "profile");
+ AA_READ_X(e, AA_DYN_STRING, &profile->name, NULL);
+
+ error_string = "Invalid flags";
+ /* per profile debug flags (debug, complain, audit) */
+ AA_READ_X(e, AA_STRUCT, NULL, "flags");
+ AA_READ_X(e, AA_U32, &(profile->flags.debug), "profile.flags.debug");
+ AA_READ_X(e, AA_U32, &(profile->flags.complain),
+ "profile.flags.complain");
+ AA_READ_X(e, AA_U32, &(profile->flags.audit), "profile.flags.audit");
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ error_string = "Invalid capabilities";
+ AA_READ_X(e, AA_U32, &(profile->capabilities), "profile.capabilities");
+
+ /* get the file entries. */
+ AA_ENTRY_LIST("pgent"); /* pcre rules */
+ AA_ENTRY_LIST("sgent"); /* simple globs */
+ AA_ENTRY_LIST("fent"); /* regular file entries */
+
+ /* get the net entries */
+ if (aa_is_nameX(e, AA_LIST, NULL, "net")) {
+ error_string = "Invalid net entry";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ if (!aa_activate_net_entry(e))
+ goto fail;
+ }
+ }
+ rulename = "";
+
+ /* get subprofiles */
+ if (aa_is_nameX(e, AA_LIST, NULL, "hats")) {
+ error_string = "Invalid profile hat";
+ while (!aa_is_nameX(e, AA_LISTEND, NULL, NULL)) {
+ struct aaprofile *subprofile;
+ subprofile = aa_activate_profile(e, error);
+ if (!subprofile)
+ goto fail;
+ subprofile->parent = profile;
+ list_add(&subprofile->list, &profile->sub);
+ }
+ }
+
+ error_string = "Invalid end of profile";
+ AA_READ_X(e, AA_STRUCTEND, NULL, NULL);
+
+ return profile;
Is this kind of transltion neccessary?
Pavel
--
Thanks, Sharp!
Arjan van de Ven
2006-04-19 18:14:22 UTC
Permalink
Post by Tony Jones
Attached patches to include the AppArmor application security module in
the linux kernel.
can you at least comment on the discussion that happened on lkml the
past few days where basically most people understood and agreed that a
path-based approach is just not possible in linux anymore...

you must have a good defense against that argument, so I'm curious to
hear what it is

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Andi Kleen
2006-04-19 22:32:43 UTC
Permalink
Post by Arjan van de Ven
you must have a good defense against that argument, so I'm curious to
hear what it is
[I'm not from the apparmor people but my understanding is]

Usually they claimed name spaces as the reason it couldn't work.

In practice AFAIK basically nobody uses name spaces for
anything. AppArmor just forbids mounts/CLONE_NEWNS for the confined
applications and there is no way a new name space can be created so
the problem doesn't exist.

I suppose if name space based user space takes over the world
there is a problem but I have yet to see any signs of plan9 like userland
taking over the world (e.g. they're certainly not used for anything in
the distributions I'm familiar with).

Some people claim it will be used in the future in a particular application
that most people I know don't want to have anything to do with,
but right now that application uses an own file system that is also unlikely
to work with selinux or anything so it won't change anything for that.

I'm not aware of any other proposed application.

Also name spaces can be still used of course, just not with
apparmor limited applications.

So it's an interesting theoretical discussion, but in practice
it's a non issue.

Anyways, I guess the bigger issue is with hard links anyways
(Chris gave a long list of other ways to get aliases in path names
earlier). Discussing those might be much more fruitful.

That said I'm personally not too fond of path named filtering
either (it always seemed ugly to me), but I haven't seen anybody
proposing anything better so far that wasn't insanely complex
and basically impossible to administer.

-Andi
grundig
1970-01-01 00:00:00 UTC
Permalink
El 20 Apr 2006 00:32:43 +0200,
Some people claim it will be used in the future in a particular appli=
cation
that most people I know don't want to have anything to do with,
but right now that application uses an own file system that is also u=
nlikely
to work with selinux or anything so it won't change anything for that=
=2E
=20
I'm not aware of any other proposed application.
However such application may exist in the future (which is why the feat=
ure
was implemented, i guess). If AppArmor becomes widespread in the future
(well suse has it anyway so it's already quite widespread) it won't be =
easy
to create succesful apps which play with namespaces, not to speak that=
it
won't be possible to "securize" such apps. From my user POV it seems
really weird that a feature forbids you from using another unrelated fe=
ature.

-
To unsubscribe from this list: send the line "unsubscribe linux-securit=
y-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Andi Kleen
2006-04-19 23:38:00 UTC
Permalink
Post by grundig
From my user POV it seems
really weird that a feature forbids you from using another unrelated feature
e.g. using a firewall usually prevents some applications
from working. Or using hugepages is not compatible with a lot of other VM
features. Or some locking doesn't work over NFS.

There are lots of things like this in the kernel like in any
complex system.

-Andi
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Crispin Cowan
2006-04-20 01:32:30 UTC
Permalink
Post by Andi Kleen
Post by grundig
From my user POV it seems
really weird that a feature forbids you from using another unrelated feature
e.g. using a firewall usually prevents some applications
from working. Or using hugepages is not compatible with a lot of other VM
features. Or some locking doesn't work over NFS.
Exactly. The basic function of an access control system is to block
access, selectively. So it is quite natural for AppArmor, and SELinux,
to block access to various kernel features at various times. Because
AppArmor is fundamentally name-based access control, changes to the name
space affect AppArmor, and thus access to changing the name space must
be managed.

Our controls on changing the name space have rather poor granularity at
the moment. We hope to improve that over time, and especially if LSM
evolves to permit it. This is ok, because as Andi pointed out, there are
currently few applications using name spaces, so we have time to improve
the granularity.

Crispin
--
Crispin Cowan, Ph.D. http://crispincowan.com/~crispin/
Director of Software Engineering, Novell http://novell.com

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
grundig
1970-01-01 00:00:00 UTC
Permalink
El Wed, 19 Apr 2006 18:32:30 -0700,
Our controls on changing the name space have rather poor granularity =
at
the moment. We hope to improve that over time, and especially if LSM
evolves to permit it. This is ok, because as Andi pointed out, there =
are
currently few applications using name spaces, so we have time to impr=
ove
the granularity.
Wouldn't have more sense to improve it and then submit it instead of th=
e
contrary? At least is the rule which AFAIK is applied to every feature=20
going in the kernel, specially when there's an available alternative
which users can use meanwhile (see reiser4...)
-
To unsubscribe from this list: send the line "unsubscribe linux-securit=
y-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Serge E. Hallyn
2006-04-20 13:09:17 UTC
Permalink
Post by grundig
El Wed, 19 Apr 2006 18:32:30 -0700,
Post by Crispin Cowan
Our controls on changing the name space have rather poor granularity at
the moment. We hope to improve that over time, and especially if LSM
evolves to permit it. This is ok, because as Andi pointed out, there are
currently few applications using name spaces, so we have time to improve
the granularity.
Wouldn't have more sense to improve it and then submit it instead of the
contrary? At least is the rule which AFAIK is applied to every feature
going in the kernel, specially when there's an available alternative
which users can use meanwhile (see reiser4...)
hah, that's funny

When people do that, they are rebuked for not submitting upstream. At
least this way, we can have a discussion about whether the approach
makes sense at all.

-serge
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Al Viro
2006-04-20 13:15:04 UTC
Permalink
Post by Serge E. Hallyn
Post by grundig
El Wed, 19 Apr 2006 18:32:30 -0700,
Post by Crispin Cowan
Our controls on changing the name space have rather poor granularity at
the moment. We hope to improve that over time, and especially if LSM
evolves to permit it. This is ok, because as Andi pointed out, there are
currently few applications using name spaces, so we have time to improve
the granularity.
Wouldn't have more sense to improve it and then submit it instead of the
contrary? At least is the rule which AFAIK is applied to every feature
going in the kernel, specially when there's an available alternative
which users can use meanwhile (see reiser4...)
hah, that's funny
When people do that, they are rebuked for not submitting upstream. At
least this way, we can have a discussion about whether the approach
makes sense at all.
Not in such form. If authors want the patches reviewed, the least they
can do is splitting them up in a way that would allow reading them
sequentially, damnit...
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Tony Jones
2006-04-21 00:11:16 UTC
Permalink
Post by Serge E. Hallyn
Post by grundig
El Wed, 19 Apr 2006 18:32:30 -0700,
Post by Crispin Cowan
Our controls on changing the name space have rather poor granularity at
the moment. We hope to improve that over time, and especially if LSM
evolves to permit it. This is ok, because as Andi pointed out, there are
currently few applications using name spaces, so we have time to improve
the granularity.
Wouldn't have more sense to improve it and then submit it instead of the
contrary? At least is the rule which AFAIK is applied to every feature
going in the kernel, specially when there's an available alternative
which users can use meanwhile (see reiser4...)
hah, that's funny
When people do that, they are rebuked for not submitting upstream. At
least this way, we can have a discussion about whether the approach
makes sense at all.
When an out of tree user is requesting a change for which it will likely be
the only user (such that it can make it in tree), caution is warranted. But
it does create a bit of a chicken and egg conundrum for the proposer.

At least this is the way it's always seemed to me (re: the requested VFS/LSM
changes). Anyways, it's not an issue and there isn't a lot of point dwelling
over past LSM history. I 100% agree with Serge, discussion first about the
approach is definately the way to go.

Tony
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Arjan van de Ven
2006-04-20 08:42:03 UTC
Permalink
Post by Andi Kleen
Post by Arjan van de Ven
you must have a good defense against that argument, so I'm curious to
hear what it is
[I'm not from the apparmor people but my understanding is]
Usually they claimed name spaces as the reason it couldn't work.
I actually posted a list of 10 things that I made up in 3 minutes; just
going over those 10 would be a good start already since they're the most
obvious ones..

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Crispin Cowan
2006-04-20 19:26:57 UTC
Permalink
Post by Arjan van de Ven
I actually posted a list of 10 things that I made up in 3 minutes; just
going over those 10 would be a good start already since they're the most
obvious ones..
I had actually posted a response to those 10 questions in the previous
"remove LSM" thread. Here it is again.
Post by Arjan van de Ven
after all what does filename mean in a linux world with
* hardlinks
If the policy lets you access /foo/bar/baz then you get to access
/foo/bar/baz, even if it is a hard link to /foo/bif.

Some would allege that this is a security "hole" in AppArmor. However,
AppArmor's design is that you only get to *create* that hard link if you
are either unconfined or your profile says you get to create it.
AppArmor implicitly trusts all non-confined processes, so anything they
do is ok, by definition.
Post by Arjan van de Ven
* chroot
In the currently shipping AppArmor that comes with SUSE Linux, the names
AppArmor sees are chroot-relative. The patch just posted fixes that and
the names AppArmor sees are now absolute, regardless of chroot jailing.
Post by Arjan van de Ven
* namespaces
* bind mounts
As far as we know, our namespace support is fine; we mediate attempts to
modify namespaces (such as denying mount and umount) and requiring
cap_sys_chroot to modify the root of the namespace. If there are
instances where we are incorrect we would greatly appreciate a detailed
description of the issue (or better a testcase) so we can look at
resolving it.
Post by Arjan van de Ven
* unlink of open files
* fd passing over unix sockets
AppArmor initially validates your access at open time, and there after
you can read&write to it without mediation. AppArmor re-validates your
access if policy is reloaded, you exec() a new program, you get passed
the fd from another process, or you call our change_hat() API.

So, if the file is unlinked or renamed while you have it open, and
policy says you don't have access to the new name, then:

* within the same process you get to keep accessing it until
o policy is reloaded by the administrator
o you call the change_hat() API
* in some other process, either a child or some process you passed
an fd to, you don't get to access it because your access gets
revalidated

Note that d_path still returns pathnames for files that have been
removed from the filesystem (that are open)
Post by Arjan van de Ven
* relative pathnames
If you access "../hosts.allow" AppArmor will canonicalize your path name
to /etc/hosts.allow before checking the policy.
Post by Arjan van de Ven
* multiple threads (where one can unlink+replace file while the other
is in the validation code)
Can you show a specific case that you think would be a problem? Security
is the problem of allowing "good stuff" and blocking "bad stuff", and
that is hard to argue for complex cases that are not specific.

Crispin
--
Crispin Cowan, Ph.D. http://crispincowan.com/~crispin/
Director of Software Engineering, Novell http://novell.com


-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Chris Wright
2006-04-20 19:27:17 UTC
Permalink
Post by Arjan van de Ven
Post by Andi Kleen
Post by Arjan van de Ven
you must have a good defense against that argument, so I'm curious to
hear what it is
[I'm not from the apparmor people but my understanding is]
Usually they claimed name spaces as the reason it couldn't work.
I actually posted a list of 10 things that I made up in 3 minutes; just
going over those 10 would be a good start already since they're the most
obvious ones..
Yes, the conversation is all over the place. Many of the issues are
about some of the uglier parts of the AppArmor code, but the critical
issue is simple. Does their protection model actually protect against
their threat model. I would really like to see some grounded examples
that show whether it's broken or not.

thanks,
-chris
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 12:18:46 UTC
Permalink
Post by Chris Wright
Post by Arjan van de Ven
Post by Andi Kleen
Post by Arjan van de Ven
you must have a good defense against that argument, so I'm curious to
hear what it is
[I'm not from the apparmor people but my understanding is]
Usually they claimed name spaces as the reason it couldn't work.
I actually posted a list of 10 things that I made up in 3 minutes; just
going over those 10 would be a good start already since they're the most
obvious ones..
Yes, the conversation is all over the place. Many of the issues are
about some of the uglier parts of the AppArmor code, but the critical
issue is simple. Does their protection model actually protect against
their threat model. I would really like to see some grounded examples
that show whether it's broken or not.
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
--
Stephen Smalley
National Security Agency
Chris Wright
2006-04-21 17:30:08 UTC
Permalink
Post by Stephen Smalley
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
I know, there's two questions. Whether the protection model is valid,
and whether the threat model is worth considering. So far, I've not
seen anything that's compelling enough to show AppArmor fundamentally
broken. Ugly and inefficient, yes...broken, not yet.

thanks,
-chris
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 18:07:33 UTC
Permalink
Post by Chris Wright
Post by Stephen Smalley
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
I know, there's two questions. Whether the protection model is valid,
and whether the threat model is worth considering. So far, I've not
seen anything that's compelling enough to show AppArmor fundamentally
broken. Ugly and inefficient, yes...broken, not yet.
Access control of any form requires unambiguous identification of
subjects and objects in the system. Paths don't achieve such
identification. Is that broken enough? If not, what is? What
qualifies as broken?
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
V***@vt.edu
2006-04-21 20:06:21 UTC
Permalink
Post by Stephen Smalley
Post by Chris Wright
Post by Stephen Smalley
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
I know, there's two questions. Whether the protection model is valid,
and whether the threat model is worth considering. So far, I've not
seen anything that's compelling enough to show AppArmor fundamentally
broken. Ugly and inefficient, yes...broken, not yet.
Access control of any form requires unambiguous identification of
subjects and objects in the system. Paths don't achieve such
identification. Is that broken enough? If not, what is? What
qualifies as broken?
I'd be willing to at least *listen* to an argument of the form "paths are
in general broken, but we have constraints X, Y, and Z on the system such
that the broken parts never manifest" (for instance, a restriction on
hardlinks that prevents hardlinking 2 files unless the resulting security
domains of the two paths would be identical).

However, I'll say up front that such an argument would only suffice to
move it from "broken" to "very brittle in face of changes" (for instance,
would such a hardlink restriction cause collateral damage that an attacker
could exploit? How badly does it fail in the face of a misdesigned policy?)
Stephen Smalley
2006-04-21 20:35:04 UTC
Permalink
Post by V***@vt.edu
Post by Stephen Smalley
Post by Chris Wright
Post by Stephen Smalley
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
I know, there's two questions. Whether the protection model is valid,
and whether the threat model is worth considering. So far, I've not
seen anything that's compelling enough to show AppArmor fundamentally
broken. Ugly and inefficient, yes...broken, not yet.
Access control of any form requires unambiguous identification of
subjects and objects in the system. Paths don't achieve such
identification. Is that broken enough? If not, what is? What
qualifies as broken?
I'd be willing to at least *listen* to an argument of the form "paths are
in general broken, but we have constraints X, Y, and Z on the system such
that the broken parts never manifest" (for instance, a restriction on
hardlinks that prevents hardlinking 2 files unless the resulting security
domains of the two paths would be identical).
IIUC, AppArmor does impose such constraints, but only from the
perspective of an individual program's profile. Upon link(2), they
check that the program had link permission to the old link name and that
both the old link name and new link name have consistent permissions in
the profile, and they prohibit or limit by capability the ability to
manipulate the namespace by confined programs. But this doesn't mean
that another program running under a different profile can't create such
a link (if allowed to do so by its profile, of course), or that an
unconfined process cannot do so. There is no real "system policy" or
system-wide security properties with AppArmor; you can only look at it
in terms of individual programs (which themselves are identified by path
too).
Post by V***@vt.edu
However, I'll say up front that such an argument would only suffice to
move it from "broken" to "very brittle in face of changes" (for instance,
would such a hardlink restriction cause collateral damage that an attacker
could exploit? How badly does it fail in the face of a misdesigned policy?)
Indeed. I think Thomas Bleher made a good assessment of it in:
https://lists.ubuntu.com/archives/ubuntu-hardened/2006-March/000143.html
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 20:44:17 UTC
Permalink
Post by Stephen Smalley
Post by V***@vt.edu
Post by Stephen Smalley
Post by Chris Wright
Post by Stephen Smalley
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
I know, there's two questions. Whether the protection model is valid,
and whether the threat model is worth considering. So far, I've not
seen anything that's compelling enough to show AppArmor fundamentally
broken. Ugly and inefficient, yes...broken, not yet.
Access control of any form requires unambiguous identification of
subjects and objects in the system. Paths don't achieve such
identification. Is that broken enough? If not, what is? What
qualifies as broken?
I'd be willing to at least *listen* to an argument of the form "paths are
in general broken, but we have constraints X, Y, and Z on the system such
that the broken parts never manifest" (for instance, a restriction on
hardlinks that prevents hardlinking 2 files unless the resulting security
domains of the two paths would be identical).
IIUC, AppArmor does impose such constraints, but only from the
perspective of an individual program's profile. Upon link(2), they
check that the program had link permission to the old link name and that
both the old link name and new link name have consistent permissions in
the profile, and they prohibit or limit by capability the ability to
manipulate the namespace by confined programs. But this doesn't mean
that another program running under a different profile can't create such
a link (if allowed to do so by its profile, of course), or that an
unconfined process cannot do so. There is no real "system policy" or
system-wide security properties with AppArmor; you can only look at it
in terms of individual programs (which themselves are identified by path
too).
Oh, and in the case, of links to programs (as opposed to data files),
the AppArmor folks have been known to encourage people to create
multiple hard links to a single program with different profiles as a way
of supporting multiple security domains for a single program, e.g.
creating multiple links to bash and using them as user shells to
establish different security domains for users.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Dave Neuer
2006-04-21 21:38:37 UTC
Permalink
Post by Stephen Smalley
IIUC, AppArmor does impose such constraints, but only from the
perspective of an individual program's profile. Upon link(2), they
check that the program had link permission to the old link name and that
both the old link name and new link name have consistent permissions in
the profile, and they prohibit or limit by capability the ability to
manipulate the namespace by confined programs. But this doesn't mean
that another program running under a different profile can't create such
a link (if allowed to do so by its profile, of course), or that an
unconfined process cannot do so. There is no real "system policy" or
system-wide security properties with AppArmor; you can only look at it
in terms of individual programs (which themselves are identified by path
too).
Post by V***@vt.edu
However, I'll say up front that such an argument would only suffice to
move it from "broken" to "very brittle in face of changes" (for instance,
would such a hardlink restriction cause collateral damage that an attacker
could exploit? How badly does it fail in the face of a misdesigned policy?)
https://lists.ubuntu.com/archives/ubuntu-hardened/2006-March/000143.html
But what about Dr. Cowan's response at:
https://lists.ubuntu.com/archives/ubuntu-hardened/2006-March/000144.html

In particular, if you don't trust your users, why do you give them the
ability to create links?

Dave
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Thomas Bleher
2006-04-22 10:01:09 UTC
Permalink
Post by Dave Neuer
Post by Stephen Smalley
IIUC, AppArmor does impose such constraints, but only from the
perspective of an individual program's profile. Upon link(2), they
check that the program had link permission to the old link name and that
both the old link name and new link name have consistent permissions in
the profile, and they prohibit or limit by capability the ability to
manipulate the namespace by confined programs. But this doesn't mean
that another program running under a different profile can't create such
a link (if allowed to do so by its profile, of course), or that an
unconfined process cannot do so. There is no real "system policy" or
system-wide security properties with AppArmor; you can only look at it
in terms of individual programs (which themselves are identified by path
too).
Post by V***@vt.edu
However, I'll say up front that such an argument would only suffice to
move it from "broken" to "very brittle in face of changes" (for instance,
would such a hardlink restriction cause collateral damage that an attacker
could exploit? How badly does it fail in the face of a misdesigned policy?)
https://lists.ubuntu.com/archives/ubuntu-hardened/2006-March/000143.html
https://lists.ubuntu.com/archives/ubuntu-hardened/2006-March/000144.html
I didn't have time to answer his mail then, but I will try to do so now.

Mr. Cowan asserts that today Linux is used mainly as a single-user
machine or a dedicated network servers and that AppArmor is designed to
secure these.
For me, this is not enough, because I also need to secure multi-user
computers and I don't want to learn a new security mechanism for these
machines. (I worked several years as a system administrator for a
university where this was my main job).

But the more important point is that, IMO, AppArmor also fails to secure
e.g. dedicated network servers. Consider for example an Apache server
where users are allowed to install CGIs. How do you prevent an attacker
from subverting a buggy CGI and using the host to send spam?
(Hint: It's really easy using standard SELinux policy).
Post by Dave Neuer
In particular, if you don't trust your users, why do you give them the
ability to create links?
Well, let's have a look at how Linux is used at e.g. university (that's
where I have the most experience). Lot's of untrusted students, doing
lots of weird things. They need to be allowed to run all sorts of
software, experiment with Linux, share files locally and so on. It's not
easy to confine such users.

On those machines, you don't care very much about individual users (if a
user is stupid enough to get his account hacked you just wipe his
homedir and restore from backup), but you do care about system integrity
and secrecy of things like /etc/shadow.
It is possible to confine such a system using SELinux (I've done it). Is
it possible using AppArmor? As far as I understood it is not. For me,
that's a severe limit in functionality.

Thomas
Neil Brown
2006-04-24 04:18:50 UTC
Permalink
Post by Stephen Smalley
Post by Chris Wright
Post by Stephen Smalley
Difficult to evaluate, when the answer whenever a flaw is pointed out is
"that's not in our threat model." Easy enough to have a protection
model match the threat model when the threat model is highly limited
(and never really documented anywhere, particularly in a way that might
warn its users of its limitations).
I know, there's two questions. Whether the protection model is valid,
and whether the threat model is worth considering. So far, I've not
seen anything that's compelling enough to show AppArmor fundamentally
broken. Ugly and inefficient, yes...broken, not yet.
Access control of any form requires unambiguous identification of
subjects and objects in the system. Paths don't achieve such
identification. Is that broken enough? If not, what is? What
qualifies as broken?
I have to disagree with this. Paths *do* achieve unambiguous
identification of something. That something is ..... the path.

Think about the name of this system for a minute. "AppArmor".
i.e. it is Armour for an Application. It protects the application.
It doesn't (as far as I can tell: I'm not an expert and don't work on
this thing) claim to protect files. It protects applications.

It protects them from doing the wrong thing - from doing something
they weren't designed to do. i.e. it protects them from being
subverted by exploiting a bug.

A large part of the behaviour of an application is the path names that
it uses and what it does with them. If an application started doing
unexpected things with unexpected paths (e.g. exec("/bin/sh") or
open("/etc/shadow",O_RDONLY)) then this is a sure sign that it has
been subverted and that AppArmor need to protect it, from itself.

Obviously the protection will not be complete. The profiles describe
what the application is expected to do, and to some extent, this
description will be in general terms. It might identify files that
can be written to, but not what will be written to them. etc.

While the protection against subversion cannot be complete, it can be
sufficient to dramatically reduce the chances of privilege
escalation. There are lots of wrong things you can get an
application to do once you find an exploitable bug. Many of these
will lead to a crash. AppArmor will not try to protect against these
(I suspect). There are substantially fewer that lead to privilege
escalation. AppArmor focusses its effort in terms of profile design
on exactly these sorts of unplanned behaviours.

So I think you still haven't given convincing evidence that AppArmor
is broken by design.

NeilBrown
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Theodore Ts'o
2006-04-24 07:03:24 UTC
Permalink
Post by Neil Brown
Think about the name of this system for a minute. "AppArmor".
i.e. it is Armour for an Application. It protects the application.
It doesn't (as far as I can tell: I'm not an expert and don't work on
this thing) claim to protect files. It protects applications.
...
Post by Neil Brown
While the protection against subversion cannot be complete, it can be
sufficient to dramatically reduce the chances of privilege
escalation. There are lots of wrong things you can get an
application to do once you find an exploitable bug. Many of these
will lead to a crash. AppArmor will not try to protect against these
(I suspect). There are substantially fewer that lead to privilege
escalation. AppArmor focusses its effort in terms of profile design
on exactly these sorts of unplanned behaviours.
So I think you still haven't given convincing evidence that AppArmor
is broken by design.
I have to agree with Neil here. I spent over 10 years doing network
security as my day job, including chairing the IP Security working
group and being a member of the IETF Security Area Directorate, before
switching over to Linux as the thing that would pay the bills, and I
can state quite authoratatively that perfect security which is never
used because it's too hard to install, maintain, and configure, isn't
worth much compared to imperfect security which is easy enough such
that users always use it by default.

The goal of protecting against broken, buggy applications is a worthy
one. If people can show that for a large set of stack overruns, or
other types of buggy applications, it is possible to evade AppArmor by
doing something clever, then AppArmor would need to be fixed or it's
not worth doing. But if it can prevent a large class of buggy
applications from allowing an atttacker to escalate that bugginess
into a system penetration, then it has added value.

In the security world, there is a huge tradition of the best being the
enemy of the good --- and the best being so painful to use that people
don't want to use it, or the moment it gets in the way (either because
of performance reasons or their application does something that
requires painful configuration of the SELinux policy files), they
deconfigure it. At which point the "best" becomes useless.

You may or may not agree with the philosophical architecture question,
but that doesn't necessarily make it "broken by design". Choice is
good; if AppArmor forces SELinux to become less painful to use and
configure, then that in the long run will be a good thing.

- Ted
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Arjan van de Ven
2006-04-24 07:14:58 UTC
Permalink
Post by Neil Brown
A large part of the behaviour of an application is the path names that
it uses and what it does with them. If an application started doing
unexpected things with unexpected paths (e.g. exec("/bin/sh") or
open("/etc/shadow",O_RDONLY)) then this is a sure sign that it has
been subverted and that AppArmor need to protect it, from itself.
does apparmor at least (offer) to kill the app when this happens?
(rationale: the app is hijacked, better kill it before it goes to do
damage)


-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Serge E. Hallyn
2006-04-20 11:29:51 UTC
Permalink
Post by Andi Kleen
Post by Arjan van de Ven
you must have a good defense against that argument, so I'm curious to
hear what it is
[I'm not from the apparmor people but my understanding is]
Usually they claimed name spaces as the reason it couldn't work.
In practice AFAIK basically nobody uses name spaces for
anything. AppArmor just forbids mounts/CLONE_NEWNS for the confined
Well, I use them all over the place to keep accounts on separate /tmp's,
etc. It may not be the norm yet, but the general availability of
pam_mount etc, and the implementation of shared subtrees may well change
that.

But then if that happens, as Al points out, AA might be able to
embrace rather than fight it.

-serge
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Christoph Hellwig
2006-04-20 13:24:54 UTC
Permalink
Post by Andi Kleen
Post by Arjan van de Ven
you must have a good defense against that argument, so I'm curious to
hear what it is
[I'm not from the apparmor people but my understanding is]
Usually they claimed name spaces as the reason it couldn't work.
In practice AFAIK basically nobody uses name spaces for
anything.
That;s mostly because we were missing feature to actually make them
usable. One of them was the shared subtree works which now is in
and will be used in practice by things like clearcase. The second big
thing we're missing is support to call mount() without addition privilegues.

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Linda A. Walsh
2006-04-20 22:32:07 UTC
Permalink
Post by Andi Kleen
Anyways, I guess the bigger issue is with hard links anyways
(Chris gave a long list of other ways to get aliases in path names
earlier). Discussing those might be much more fruitful.
Can't speak to a list I haven't seen, but hard links are not
a problem. Hard links can only be used within a volume. Simply
place all your allowed executables on one partition/volume.
Perhaps it is mounted read/only from a DVD or over an NFS share.
Hard links become a non problem if users can't write to the volume
that the files reside on.

Linda

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-20 12:17:03 UTC
Permalink
Post by Tony Jones
AppArmor is an LSM security enhancement for the Linux kernel. The
primary goal of AppArmor is to make it easy for a system administrator
to control application behavior, enforcing that the application has
access to only the files and POSIX.1e draft capabilities it requires to
do its job. AppArmor deliberately uses this simple access control model
to make it as easy as possible for the administrator to manage the
policy, because the worst security of all is that which is never
deployed because it was too hard.
The worst security is that which doesn't do what it claims to do, giving
a false sense of security. Which is precisely the problem with
path-based access control; it makes the user think that he is protecting
a given object, when in fact the object may be accessible by another
means.
Post by Tony Jones
AppArmor chooses which security policy to enforce for a process at
exec() time by the executable image's pathname, in conjunction with any
policy enforced for the currently running executable.
Multiple instances of the same program can't be distinguished?
Post by Tony Jones
AppArmor mediates access to the file system using absolute path names
with shell-syntax wildcards, so that "/srv/htdocs/** r" grants read
access to all files in /srv/htdocs.
So you have an unbounded number of policies that can govern access to
any given file object and the enforcement of the system policy is
entirely dependent on the file tree structure and any process that can
manipulate that structure. Further, what about runtime files generated
in shared directories like /tmp, /var/run, ... And let's please
separate user interface from kernel mechanism. If you want to specify
pathnames for fixed resources in a user-level config file, that is fine,
but pathnames have historically been rejected as a suitable basis for
the kernel for a reason, not just in the security space. Why should
this change? Isn't this a fundamental design change to Linux?
Post by Tony Jones
AppArmor mediates access to POSIX.1e
Capabilities in that the process must both have e.g. "capability
net_bind_service" and intrinsically have that capability (usually by
being root) to be able to bind to privileged network ports. Thus a
confined process can not subvert AppArmor except as permitted by policy,
Not sure what is meant by "subvert AppArmor" here. Bypass is certainly
possible.
Post by Tony Jones
AppArmor is *not* intended to protect every aspect of the system from
every other aspect of the system: the intended usage is that only a
small fraction of all programs on a Linux system will have AppArmor
profiles. Rather, AppArmor is intended to protect the system against a
particular threat.
So the attacker knows precisely how to bypass it. The attacker (beyond
the script kiddies) isn't going to attack you at the point of strength;
he will attack you where you are known to be weak.
Post by Tony Jones
For instance, to secure a machine against network attack, all programs
that face the network should be profiled. If all open network ports lead
to AppArmor profiles, then there is no way for the network attacker to
attack the machine, except as controlled by AppArmor policy. As a
convenience, AppArmor includes a netstat wrapper that reports all
programs with open network ports and their AppArmor profile status.
Except that your internal unconfined programs/processes may nonetheless
be subverted via the confined program, because:
a) you aren't controlling all objects and operations, and
b) you aren't controlling those internal programs/processes at all, so
they are at risk of taking untrustworthy inputs from the "confined"
ones, and
c) there is a high likelihood of interactions between those
processes/programs in any real system, particularly in shared
directories where paths aren't very useful as a distinguisher.
Post by Tony Jones
2. AppArmor needs to re-construct the full path name of files to
perform initial validation. Some of the LSM hooks that we mediate
do not have vfsmount/nameidata passed. Our temporary workaround is
to export the namespace_sem semaphore so we can safely walk the
process's namespace to find a vfsmount with a root dentry matching
the dentry we are trying to mediate. We believe a cleaner solution
(such as passing a vfsmount or nameidata to all LSM hooks throughout
the VFS layer) would be useful for audit, other LSMs, and
potentially FUSE. As it is a fair amount of work to pass vfsmount or
nameidata structures throughout the VFS, alternative suggestions
and ideas are welcomed.
Introduce new hooks at the proper location where the information is available.
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Joshua Brindle
2006-04-20 15:38:53 UTC
Permalink
Post by Stephen Smalley
Post by Tony Jones
AppArmor is an LSM security enhancement for the Linux kernel. The
primary goal of AppArmor is to make it easy for a system administrator
to control application behavior, enforcing that the application has
access to only the files and POSIX.1e draft capabilities it requires to
do its job. AppArmor deliberately uses this simple access control model
to make it as easy as possible for the administrator to manage the
policy, because the worst security of all is that which is never
deployed because it was too hard.
The worst security is that which doesn't do what it claims to do, giving
a false sense of security. Which is precisely the problem with
path-based access control; it makes the user think that he is protecting
a given object, when in fact the object may be accessible by another
means.
I've compiled a list of security related issues with path based access
control at
http://securityblog.org/brindle/2006/04/19/security-anti-pattern-path-based-access-control/

I intentionally avoided specific implementations and OS related issues
to focus on the security aspects. Note that not all path based access
control implementations are subject to all these problems but some are
common to all.

Joshua Brindle

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Crispin Cowan
2006-04-20 19:57:32 UTC
Permalink
Post by Stephen Smalley
Post by Tony Jones
AppArmor is an LSM security enhancement for the Linux kernel. The
primary goal of AppArmor is to make it easy for a system administrator
to control application behavior, enforcing that the application has
access to only the files and POSIX.1e draft capabilities it requires to
do its job. AppArmor deliberately uses this simple access control model
to make it as easy as possible for the administrator to manage the
policy, because the worst security of all is that which is never
deployed because it was too hard.
The worst security is that which doesn't do what it claims to do, giving
a false sense of security. Which is precisely the problem with
path-based access control; it makes the user think that he is protecting
a given object, when in fact the object may be accessible by another
means.
Perhaps in some implementation, but not in AppArmor:

* AppArmor access controls are bound to processes, not files. The
control is to contain the subject, not shield the object.
* AppArmor uses a white list, not a black list. "By another means"
assumes that you have another means, and with the white list of
accessible file names, you generally don't.

Please show a case where AppArmor is not doing what it claims to do, or
stop spreading the FUD.

Consider Lampson's classic access control matrix, where you have all
your subjects (processes doing the operations) across one edge, and all
your objects (files and processes being operated on) along the other
edge, and the matrix cells contain permitted operations. Lampson's
matrix is the maximally expressive form of access control, but it is so
large as to be unmanageable. All access control schemes are abstractions
on this space to simplify it to make it manageable.

AppArmor is a particular abstraction on this space, where we profile
each row (process) by what application it runs, and then list the
columns (files) it can access, and in what mode. This is the exact dual
of ACLs, which put access lists on a file of who can access them. This
makes AppArmor a kind of capability system (in the security theory-head
way, nothing to do with POSIX.1e "capabilities"). However, because
confined entities cannot pass around AppArmor permissions as first-class
objects, it is called an "ambient capability" system.
Post by Stephen Smalley
Post by Tony Jones
AppArmor chooses which security policy to enforce for a process at
exec() time by the executable image's pathname, in conjunction with any
policy enforced for the currently running executable.
Multiple instances of the same program can't be distinguished?
They can be distinguished by the context you call them from. If your
policy says "/bin/foo px" then it uses the system profile for /bin/foo.
If your policy says "/bin/foo ix" then it inherits the parent's profile.
If your policy says "/bin/foo ux" then it executes unconstrained.

We are designing features to allow more ways to execute the same program
under different profiles in different contexts. But these features
require no new hooks, and so should not concern kernel developers, and
only be of interest to people who plan to use AppArmor.
Post by Stephen Smalley
Post by Tony Jones
AppArmor mediates access to the file system using absolute path names
with shell-syntax wildcards, so that "/srv/htdocs/** r" grants read
access to all files in /srv/htdocs.
So you have an unbounded number of policies that can govern access to
any given file object and the enforcement of the system policy is
entirely dependent on the file tree structure and any process that can
manipulate that structure.
No; to the contrary, the above policy means the same thing on every file
system: if any file matching /srv/htdocs/** exists, then the contained
process can access it.

On the other hand, SELinux policy describes whether or not one label can
access another, and the meaning of that policy is dependent on the
existence and state of the labels in the file system. Tar and restore
your directory tree and the SELinux policy meaning changes. Labels on
the filesystem can change over time; so that running:
find / -exec ls -Z {} \; | grep httpd_user_content_t

will only show a snapshot of what would be allowed *right now*. So if
anything, SELinux is more sensitive to file system state than AppArmor,
and in any meaningful sense has many more possible policies enforced on
the system.
Post by Stephen Smalley
Further, what about runtime files generated
in shared directories like /tmp, /var/run, ...
What about them? If your program needs access to /tmp and /var, then
your policy should grant it. The above is a single line out of a
profile. The Apache profile is about 150 lines.

AppArmor is sufficiently expressive that you can give permission to
create /tmp/foo but *not* /tmp/bar. In SELinux, applications can create
/tmp files and give them labels different than the default label, but
AFAIK, SELinux can only either grant or deny permission to create a file
in a given directory, and not which file you can create.

You might care about this because the semantics of /etc/hosts.allow are
rather different than /etc/mumblebarf.
Post by Stephen Smalley
And let's please
separate user interface from kernel mechanism. If you want to specify
pathnames for fixed resources in a user-level config file, that is fine,
but pathnames have historically been rejected as a suitable basis for
the kernel for a reason, not just in the security space. Why should
this change? Isn't this a fundamental design change to Linux?
Because pathname matching in the kernel is fundamentally what makes
AppArmor easy to use. It is also what prevents AppArmor from being
implemented on top of SELinux.
Post by Stephen Smalley
Post by Tony Jones
AppArmor mediates access to POSIX.1e
Capabilities in that the process must both have e.g. "capability
net_bind_service" and intrinsically have that capability (usually by
being root) to be able to bind to privileged network ports. Thus a
confined process can not subvert AppArmor except as permitted by policy,
Not sure what is meant by "subvert AppArmor" here.
"Compromise" means a confined process being able to modify or disable
AppArmor policy.
Post by Stephen Smalley
Bypass is certainly possible.
An interesting claim. We disagree, other than possibly coding errors.
Can you provide an actual example of bypass?
Post by Stephen Smalley
Post by Tony Jones
AppArmor is *not* intended to protect every aspect of the system from
every other aspect of the system: the intended usage is that only a
small fraction of all programs on a Linux system will have AppArmor
profiles. Rather, AppArmor is intended to protect the system against a
particular threat.
So the attacker knows precisely how to bypass it. The attacker (beyond
the script kiddies) isn't going to attack you at the point of strength;
he will attack you where you are known to be weak.
If you properly configured your system to confine all exposed programs,
then the attacker *must* start from a confined program, so this is a
non-issue.

Please keep in mind that our security goals are different from what
you've considered in the past; a system administrator will confine
programs that he or she considers are a higher than tolerable risk. We
provide an easy and reliable mechanism to confine software that poses
unreasonable risk. We do not try to provide provable information flow
properties.
Post by Stephen Smalley
Post by Tony Jones
For instance, to secure a machine against network attack, all programs
that face the network should be profiled. If all open network ports lead
to AppArmor profiles, then there is no way for the network attacker to
attack the machine, except as controlled by AppArmor policy. As a
convenience, AppArmor includes a netstat wrapper that reports all
programs with open network ports and their AppArmor profile status.
Except that your internal unconfined programs/processes may nonetheless
a) you aren't controlling all objects and operations, and
b) you aren't controlling those internal programs/processes at all, so
they are at risk of taking untrustworthy inputs from the "confined"
ones, and
That you could subvert a non-confined program from a confined program is
conjecture; you have to suppose that the non-confined program will
*listen* to assorted noise coming from the confined program. Certainly
you can build a covert channel that can leak data from a confined
process to a non-confined process, but only if the non-confined process
is listening. We mediate things like ptrace that would allow the
confined process to force the non-confined process to "listen".

And if you are concerned about a particular program being so sensitive
to its environment that it could be subverted, then confine it too.

Security is always a balance between convenience and, well, security :)
You can make an AppArmor system more secure and less convenient by
confining more programs and using more specific pathnames in the
profiles. Conversely, you can make it more convenient and less secure by
confining fewer programs and using path names with more wildcards.
Making the scalable decision between security and convenience available
to the user is an important feature of AppArmor.
Post by Stephen Smalley
c) there is a high likelihood of interactions between those
processes/programs in any real system, particularly in shared
directories where paths aren't very useful as a distinguisher.
How so? We find them to be a very useful distinguisher. Lots and lots of
people find them to be a very useful distinguisher. In fact, when
configuring AppArmor policy, our users have been surprised at what
exactly their (often in-house, usually proprietary) applications access
-- and have been grateful for a tool that can make these distinctions.
Post by Stephen Smalley
Post by Tony Jones
2. AppArmor needs to re-construct the full path name of files to
perform initial validation. Some of the LSM hooks that we mediate
do not have vfsmount/nameidata passed. Our temporary workaround is
to export the namespace_sem semaphore so we can safely walk the
process's namespace to find a vfsmount with a root dentry matching
the dentry we are trying to mediate. We believe a cleaner solution
(such as passing a vfsmount or nameidata to all LSM hooks throughout
the VFS layer) would be useful for audit, other LSMs, and
potentially FUSE. As it is a fair amount of work to pass vfsmount or
nameidata structures throughout the VFS, alternative suggestions
and ideas are welcomed.
Introduce new hooks at the proper location where the information is available.
The usual response to a request for a new hook is "where is the user?"
We want AppArmor to be in-tree as a first-class user of LSM, and then we
can discuss new hooks, perhaps as an alternative to properly supplying
vfsmount or nameidata to all of the existing hooks, which ever is more
elegant and convenient to the kernel maintainers.

Crispin
--
Crispin Cowan, Ph.D. http://crispincowan.com/~crispin/
Director of Software Engineering, Novell http://novell.com

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Stephen Smalley
2006-04-21 13:34:31 UTC
Permalink
Post by Crispin Cowan
* AppArmor access controls are bound to processes, not files. The
control is to contain the subject, not shield the object.
* AppArmor uses a white list, not a black list. "By another means"
assumes that you have another means, and with the white list of
accessible file names, you generally don't.
Please show a case where AppArmor is not doing what it claims to do, or
stop spreading the FUD.
I'm not the one spreading FUD.

If you read one of AppArmor's whitelist profiles and conclude that
because /etc/shadow is not listed in it, your shadow password data is
protected against reading via a compromise of that program, then you
might find yourself unpleasantly surprised. You can't tell that from
any AppArmor profile; you only know that if said process tries to open
the path "/etc/shadow", it should be denied access. You don't know
whether any other path in that same profile might ever refer to that
same file, or whether the shadow data might be exposed to said process
via a non-file object (e.g. shared memory) being used by programs
legitimately accessing the shadow data, or whether said process might be
able to compromise another process on the same system via an
uncontrolled mechanism and gain access in that manner.

End users aren't going to recognize this limitation unless you spell it
out in your end user documentation. They will believe that their shadow
password data is now safe, because they've read the very simple profile
for their program and it doesn't include "/etc/shadow" anywhere. They
will be disappointed when they find that their shadow password data has
leaked. And please don't assume that this only applies to
confidentiality/secrecy concerns; the same reasoning applies to
protecting the integrity of their systems.
Post by Crispin Cowan
Consider Lampson's classic access control matrix, where you have all
your subjects (processes doing the operations) across one edge, and all
your objects (files and processes being operated on) along the other
edge, and the matrix cells contain permitted operations. Lampson's
matrix is the maximally expressive form of access control, but it is so
large as to be unmanageable. All access control schemes are abstractions
on this space to simplify it to make it manageable.
AppArmor is a particular abstraction on this space, where we profile
each row (process) by what application it runs, and then list the
columns (files) it can access, and in what mode.
There is an assumption in the access matrix that the subjects and
objects are accurately and unambiguously identified. That is achieved
when the subjects and objects are labeled by the kernel and the labels
are used for the access control, or when unique identifiers are used for
the subject and object and used for the access control. It is not
achieved via pathnames. Therein lies the problem.
Post by Crispin Cowan
Post by Stephen Smalley
So you have an unbounded number of policies that can govern access to
any given file object and the enforcement of the system policy is
entirely dependent on the file tree structure and any process that can
manipulate that structure.
No; to the contrary, the above policy means the same thing on every file
system: if any file matching /srv/htdocs/** exists, then the contained
process can access it.
That seems to miss my point, which was that a single file object (i.e.
inode) may be accessible under multiple paths, and thus a single process
that accesses it may get different permissions depending on which path
it used, yielding multiple policies for the same object. And
rearrangement of the file tree alters the effective policy that is
applied to a given file object, as it is path-based rather than
object-based.
Post by Crispin Cowan
On the other hand, SELinux policy describes whether or not one label can
access another, and the meaning of that policy is dependent on the
existence and state of the labels in the file system.
No, the meaning of the policy is not dependent on the filesystem state;
you can analyze the policy separate from the filesystem state and know
definitively whether or not e.g. high integrity data can ever be
corrupted by a low integrity source, or whether secret data can ever be
leaked to an insecure sink. The policy is in terms of abstractions /
security equivalence classes, because you are trying to enforce higher
level security goals re confidentiality and integrity, not just whether
program X can read file F.
Post by Crispin Cowan
Tar and restore
your directory tree and the SELinux policy meaning changes. Labels on
find / -exec ls -Z {} \; | grep httpd_user_content_t
will only show a snapshot of what would be allowed *right now*. So if
anything, SELinux is more sensitive to file system state than AppArmor,
and in any meaningful sense has many more possible policies enforced on
the system.
No, the meaning of the policy doesn't change. The precise set of
objects that fall within a given security equivalence class can change
subject to the control of the policy (so even such changes fall within
the scope of an analysis of the policy, and do not alter the information
flow analysis). Now, if your concern is with file relabeling, I'd agree
that isn't desirable. The goal is to ensure that data is labeled
correctly when it is created, and preserved throughout its lifetime.
File relabeling is usually an indication of a defect in policy or
userspace, and should be gradually eradicated from the normal runtime
operation of the system.
Post by Crispin Cowan
Post by Stephen Smalley
Further, what about runtime files generated
in shared directories like /tmp, /var/run, ...
What about them? If your program needs access to /tmp and /var, then
your policy should grant it. The above is a single line out of a
profile. The Apache profile is about 150 lines.
Runtime files often don't have well-defined names, e.g. randomly
generated components, pids, arbitrary choices of users, etc. So
path-based configuration runs into problems, whereas labeled objects
reflect the security properties of their creators and thus already are
protected properly.
Post by Crispin Cowan
AppArmor is sufficiently expressive that you can give permission to
create /tmp/foo but *not* /tmp/bar. In SELinux, applications can create
/tmp files and give them labels different than the default label, but
AFAIK, SELinux can only either grant or deny permission to create a file
in a given directory, and not which file you can create.
In SELinux, newly created files are labeled in accordance with policy
and reflect the security properties of their creators.
Thus, /tmp/xyGp98x if created by sendmail would have a different
security label than the same path if created by named, and policy can
ensure that only the appropriate process can access that file.
Post by Crispin Cowan
You might care about this because the semantics of /etc/hosts.allow are
rather different than /etc/mumblebarf.
Yes, but this can be addressed either by providing distinct tools for
manipulation of objects that require distinct protection (and
configuring policy to label the objects created by those tools
accordingly), or by just instrumenting your basic editing tools to
preserve security attributes in the same way they already preserve file
modes or ACLs (a fair amount of which has been done for SELinux in at
least some distros, albeit not complete). More work? Sure. But the
right technical approach.
Post by Crispin Cowan
Post by Stephen Smalley
And let's please
separate user interface from kernel mechanism. If you want to specify
pathnames for fixed resources in a user-level config file, that is fine,
but pathnames have historically been rejected as a suitable basis for
the kernel for a reason, not just in the security space. Why should
this change? Isn't this a fundamental design change to Linux?
Because pathname matching in the kernel is fundamentally what makes
AppArmor easy to use. It is also what prevents AppArmor from being
implemented on top of SELinux.
But it is also fundamentally contrary to the Unix (and more so Linux)
model, IMHO. Naturally, not my call to make, but it seems problematic,
as this kind of approach has been rejected before.
Post by Crispin Cowan
Post by Stephen Smalley
Bypass is certainly possible.
An interesting claim. We disagree, other than possibly coding errors.
Can you provide an actual example of bypass?
I'm not talking about implementation bugs in AppArmor; I mean the
incomplete mediation and inaccurate basis of security decisions in
AppArmor. Examples given already include the subversion of the system
via an uncontrolled mechanism like local IPC or being able to access the
same data under another path. That is a bypass of the intended
protection (or at least the protection that would be expected by your
users, barring a big fat disclaimer on your product literature).
Post by Crispin Cowan
Post by Stephen Smalley
So the attacker knows precisely how to bypass it. The attacker (beyond
the script kiddies) isn't going to attack you at the point of strength;
he will attack you where you are known to be weak.
If you properly configured your system to confine all exposed programs,
then the attacker *must* start from a confined program, so this is a
non-issue.
But that doesn't help when you don't control all operations (hence
bypass via uncontrolled mechanisms) and when your basis of control is
ambiguous.
Post by Crispin Cowan
Please keep in mind that our security goals are different from what
you've considered in the past; a system administrator will confine
programs that he or she considers are a higher than tolerable risk. We
provide an easy and reliable mechanism to confine software that poses
unreasonable risk. We do not try to provide provable information flow
properties.
If you can't control information flow, then you can't provide any real
confidentiality or integrity guarantees. If I wanted a reliable
mechanism with minimal impact on userspace and didn't want to use
SELinux for some reason, I'd use a virtualization technique, not
AppArmor.
Post by Crispin Cowan
That you could subvert a non-confined program from a confined program is
conjecture; you have to suppose that the non-confined program will
*listen* to assorted noise coming from the confined program.
That isn't unlikely. As just one trivial example, malicious symlink
attacks are all about tricking a more privileged program into relying on
untrustworthy inputs.
Post by Crispin Cowan
And if you are concerned about a particular program being so sensitive
to its environment that it could be subverted, then confine it too.
AppArmor doesn't seem to generalize too well to full system protection.
Post by Crispin Cowan
Security is always a balance between convenience and, well, security :)
You can make an AppArmor system more secure and less convenient by
confining more programs and using more specific pathnames in the
profiles. Conversely, you can make it more convenient and less secure by
confining fewer programs and using path names with more wildcards.
Making the scalable decision between security and convenience available
to the user is an important feature of AppArmor.
But you are limited in your achievable security by the mechanism in
AppArmor (again, incomplete mediation and inaccurate basis for security
decisions). SELinux provides genuine scalability here, from a minimal
targeted policy through a highly strict one, all with the same
mechanism.
Post by Crispin Cowan
Post by Stephen Smalley
c) there is a high likelihood of interactions between those
processes/programs in any real system, particularly in shared
directories where paths aren't very useful as a distinguisher.
How so? We find them to be a very useful distinguisher. Lots and lots of
people find them to be a very useful distinguisher. In fact, when
configuring AppArmor policy, our users have been surprised at what
exactly their (often in-house, usually proprietary) applications access
-- and have been grateful for a tool that can make these distinctions.
For fixed resources, paths are ok (in userspace config files, not kernel
mechanism). But for runtime files created in shared directories, the
name is often dynamically generated or arbitrarily selected, and doesn't
tell us anything about the real security properties.
Post by Crispin Cowan
The usual response to a request for a new hook is "where is the user?"
We want AppArmor to be in-tree as a first-class user of LSM, and then we
can discuss new hooks, perhaps as an alternative to properly supplying
vfsmount or nameidata to all of the existing hooks, which ever is more
elegant and convenient to the kernel maintainers.
Usually you have to fix your implementation before you can get it
in-tree. We had to do that for SELinux, e.g. migrating from our own
file labeling implementation to using xattrs. I would argue that your
current implementation is a misuse of the current LSM interfaces. Not
my call to make, of course, but if that is the prevailing opinion, you
should have to rework your implementation first, and get the necessary
dependencies in place. All of which presumes that a path-based
mechanism is acceptable at all (again, not my call to make).
--
Stephen Smalley
National Security Agency

-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Pavel Machek
2006-04-22 12:27:33 UTC
Permalink
Hi!

I must say that I do not like this code. I created something similar,
ptrace-based, long time ago. It was called subterfugue.sf.net, and it
was easy-to-use; but it was also very un-unixy, slow, and gross hack.

Unfortunately AA only fixes the 'slow' part by moving it to the
kernel.
Post by Tony Jones
AppArmor mediates access to the file system using absolute path names
with shell-syntax wildcards, so that "/srv/htdocs/** r" grants read
access to all files in /srv/htdocs. AppArmor mediates access to POSIX.1e
shell-syntax-parser in kernel is not cool.
Post by Tony Jones
AppArmor is strictly monotonic to security: it only restricts privilege,
never enhancing privilege. So if you add AppArmor to a system, it only
becomes more secure or stays the same, the security policy will not add
Not true, as sendmail hole showed long time ago. Error paths tend to
be untested, and AA is very capable of unmasking such bugs.
Post by Tony Jones
AppArmor is *not* intended to protect every aspect of the system from
every other aspect of the system: the intended usage is that only a
small fraction of all programs on a Linux system will have AppArmor
profiles. Rather, AppArmor is intended to protect the system against a
particular threat.
If it is not scalable to whole system, why bother?

If it is only used for small part of system, why not use subterfugue?
Recently patches were proposed to improve ptrace performance a lot...
Post by Tony Jones
Who Needs This?
-------------------
AppArmor is a core part of SUSE Linux.
It is part of suse linux, but I'd not call it core part.

Pavel
--
Thanks, Sharp!
-
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to ***@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Continue reading on narkive:
Loading...