lkml.org 
[lkml]   [2006]   [Apr]   [19]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Date
Subject[RFC][PATCH 7/11] security: AppArmor - Misc (capabilities, data structures)
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 <tonyj@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-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
\
 
 \ /
  Last update: 2006-04-19 20:00    [W:0.255 / U:0.276 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site