[RFC][PATCH 7/11] security: AppArmor - Misc (capabilities, data structures)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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 <[email protected]>

---
 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 [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux