Messages in this thread Patch in this message | | | From | Tony Jones <> | Date | Wed, 19 Apr 2006 10:50:02 -0700 | 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/
| |