diff options
Diffstat (limited to 'security')
69 files changed, 3407 insertions, 1873 deletions
diff --git a/security/Kconfig b/security/Kconfig index 118f4549404e..d900f47eaa68 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -158,6 +158,41 @@ config HARDENED_USERCOPY_PAGESPAN been removed. This config is intended to be used only while trying to find such users. +config STATIC_USERMODEHELPER + bool "Force all usermode helper calls through a single binary" + help + By default, the kernel can call many different userspace + binary programs through the "usermode helper" kernel + interface. Some of these binaries are statically defined + either in the kernel code itself, or as a kernel configuration + option. However, some of these are dynamically created at + runtime, or can be modified after the kernel has started up. + To provide an additional layer of security, route all of these + calls through a single executable that can not have its name + changed. + + Note, it is up to this single binary to then call the relevant + "real" usermode helper binary, based on the first argument + passed to it. If desired, this program can filter and pick + and choose what real programs are called. + + If you wish for all usermode helper programs are to be + disabled, choose this option and then set + STATIC_USERMODEHELPER_PATH to an empty string. + +config STATIC_USERMODEHELPER_PATH + string "Path to the static usermode helper binary" + depends on STATIC_USERMODEHELPER + default "/sbin/usermode-helper" + help + The binary called by the kernel when any usermode helper + program is wish to be run. The "real" application's name will + be in the first argument passed to this program on the command + line. + + If you wish for all usermode helper programs to be disabled, + specify an empty string here (i.e. ""). + source security/selinux/Kconfig source security/smack/Kconfig source security/tomoyo/Kconfig diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index be5e9414a295..b6b68a7750ce 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -36,7 +36,6 @@ config SECURITY_APPARMOR_HASH select CRYPTO select CRYPTO_SHA1 default y - help This option selects whether introspection of loaded policy is available to userspace via the apparmor filesystem. @@ -45,7 +44,6 @@ config SECURITY_APPARMOR_HASH_DEFAULT bool "Enable policy hash introspection by default" depends on SECURITY_APPARMOR_HASH default y - help This option selects whether sha1 hashing of loaded policy is enabled by default. The generation of sha1 hashes for @@ -54,3 +52,32 @@ config SECURITY_APPARMOR_HASH_DEFAULT however it can slow down policy load on some devices. In these cases policy hashing can be disabled by default and enabled only if needed. + +config SECURITY_APPARMOR_DEBUG + bool "Build AppArmor with debug code" + depends on SECURITY_APPARMOR + default n + help + Build apparmor with debugging logic in apparmor. Not all + debugging logic will necessarily be enabled. A submenu will + provide fine grained control of the debug options that are + available. + +config SECURITY_APPARMOR_DEBUG_ASSERTS + bool "Build AppArmor with debugging asserts" + depends on SECURITY_APPARMOR_DEBUG + default y + help + Enable code assertions made with AA_BUG. These are primarily + function entry preconditions but also exist at other key + points. If the assert is triggered it will trigger a WARN + message. + +config SECURITY_APPARMOR_DEBUG_MESSAGES + bool "Debug messages enabled by default" + depends on SECURITY_APPARMOR_DEBUG + default n + help + Set the default value of the apparmor.debug kernel parameter. + When enabled, various debug messages will be logged to + the kernel message buffer. diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index d693df874818..ad369a7aac24 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -4,7 +4,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ - resource.o sid.o file.o + resource.o secid.o file.o policy_ns.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o clean-files := capability_names.h rlim_names.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 5923d5665209..41073f70eb41 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -18,9 +18,12 @@ #include <linux/module.h> #include <linux/seq_file.h> #include <linux/uaccess.h> +#include <linux/mount.h> #include <linux/namei.h> #include <linux/capability.h> #include <linux/rcupdate.h> +#include <uapi/linux/major.h> +#include <linux/fs.h> #include "include/apparmor.h" #include "include/apparmorfs.h" @@ -28,7 +31,9 @@ #include "include/context.h" #include "include/crypto.h" #include "include/policy.h" +#include "include/policy_ns.h" #include "include/resource.h" +#include "include/policy_unpack.h" /** * aa_mangle_name - mangle a profile name to std profile layout form @@ -37,7 +42,7 @@ * * Returns: length of mangled name */ -static int mangle_name(char *name, char *target) +static int mangle_name(const char *name, char *target) { char *t = target; @@ -71,7 +76,6 @@ static int mangle_name(char *name, char *target) /** * aa_simple_write_to_buffer - common routine for getting policy from user - * @op: operation doing the user buffer copy * @userbuf: user buffer to copy data from (NOT NULL) * @alloc_size: size of user buffer (REQUIRES: @alloc_size >= @copy_size) * @copy_size: size of data to copy from user buffer @@ -80,31 +84,29 @@ static int mangle_name(char *name, char *target) * Returns: kernel buffer containing copy of user buffer data or an * ERR_PTR on failure. */ -static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, - size_t alloc_size, size_t copy_size, - loff_t *pos) +static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf, + size_t alloc_size, + size_t copy_size, + loff_t *pos) { - char *data; + struct aa_loaddata *data; - BUG_ON(copy_size > alloc_size); + AA_BUG(copy_size > alloc_size); if (*pos != 0) /* only writes from pos 0, that is complete writes */ return ERR_PTR(-ESPIPE); - /* - * Don't allow profile load/replace/remove from profiles that don't - * have CAP_MAC_ADMIN - */ - if (!aa_may_manage_policy(op)) - return ERR_PTR(-EACCES); - /* freed by caller to simple_write_to_buffer */ - data = kvmalloc(alloc_size); + data = kvmalloc(sizeof(*data) + alloc_size); if (data == NULL) return ERR_PTR(-ENOMEM); + kref_init(&data->count); + data->size = copy_size; + data->hash = NULL; + data->abi = 0; - if (copy_from_user(data, userbuf, copy_size)) { + if (copy_from_user(data->data, userbuf, copy_size)) { kvfree(data); return ERR_PTR(-EFAULT); } @@ -112,25 +114,43 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, return data; } - -/* .load file hook fn to load policy */ -static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, - loff_t *pos) +static ssize_t policy_update(int binop, const char __user *buf, size_t size, + loff_t *pos, struct aa_ns *ns) { - char *data; ssize_t error; + struct aa_loaddata *data; + struct aa_profile *profile = aa_current_profile(); + const char *op = binop == PROF_ADD ? OP_PROF_LOAD : OP_PROF_REPL; + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(profile, ns, op); + if (error) + return error; - data = aa_simple_write_to_buffer(OP_PROF_LOAD, buf, size, size, pos); - + data = aa_simple_write_to_buffer(buf, size, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - error = aa_replace_profiles(data, size, PROF_ADD); - kvfree(data); + error = aa_replace_profiles(ns ? ns : profile->ns, profile, + binop, data); + aa_put_loaddata(data); } return error; } +/* .load file hook fn to load policy */ +static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, + loff_t *pos) +{ + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(PROF_ADD, buf, size, pos, ns); + + aa_put_ns(ns); + + return error; +} + static const struct file_operations aa_fs_profile_load = { .write = profile_load, .llseek = default_llseek, @@ -140,15 +160,10 @@ static const struct file_operations aa_fs_profile_load = { static ssize_t profile_replace(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; - ssize_t error; + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + int error = policy_update(PROF_REPLACE, buf, size, pos, ns); - data = aa_simple_write_to_buffer(OP_PROF_REPL, buf, size, size, pos); - error = PTR_ERR(data); - if (!IS_ERR(data)) { - error = aa_replace_profiles(data, size, PROF_REPLACE); - kvfree(data); - } + aa_put_ns(ns); return error; } @@ -162,22 +177,34 @@ static const struct file_operations aa_fs_profile_replace = { static ssize_t profile_remove(struct file *f, const char __user *buf, size_t size, loff_t *pos) { - char *data; + struct aa_loaddata *data; + struct aa_profile *profile; ssize_t error; + struct aa_ns *ns = aa_get_ns(f->f_inode->i_private); + + profile = aa_current_profile(); + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(profile, ns, OP_PROF_RM); + if (error) + goto out; /* * aa_remove_profile needs a null terminated string so 1 extra * byte is allocated and the copied data is null terminated. */ - data = aa_simple_write_to_buffer(OP_PROF_RM, buf, size + 1, size, pos); + data = aa_simple_write_to_buffer(buf, size + 1, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - data[size] = 0; - error = aa_remove_profiles(data, size); - kvfree(data); + data->data[size] = 0; + error = aa_remove_profiles(ns ? ns : profile->ns, profile, + data->data, size); + aa_put_loaddata(data); } - + out: + aa_put_ns(ns); return error; } @@ -186,6 +213,144 @@ static const struct file_operations aa_fs_profile_remove = { .llseek = default_llseek, }; +/** + * query_data - queries a policy and writes its data to buf + * @buf: the resulting data is stored here (NOT NULL) + * @buf_len: size of buf + * @query: query string used to retrieve data + * @query_len: size of query including second NUL byte + * + * The buffers pointed to by buf and query may overlap. The query buffer is + * parsed before buf is written to. + * + * The query should look like "<LABEL>\0<KEY>\0", where <LABEL> is the name of + * the security confinement context and <KEY> is the name of the data to + * retrieve. <LABEL> and <KEY> must not be NUL-terminated. + * + * Don't expect the contents of buf to be preserved on failure. + * + * Returns: number of characters written to buf or -errno on failure + */ +static ssize_t query_data(char *buf, size_t buf_len, + char *query, size_t query_len) +{ + char *out; + const char *key; + struct aa_profile *profile; + struct aa_data *data; + u32 bytes, blocks; + __le32 outle32; + + if (!query_len) + return -EINVAL; /* need a query */ + + key = query + strnlen(query, query_len) + 1; + if (key + 1 >= query + query_len) + return -EINVAL; /* not enough space for a non-empty key */ + if (key + strnlen(key, query + query_len - key) >= query + query_len) + return -EINVAL; /* must end with NUL */ + + if (buf_len < sizeof(bytes) + sizeof(blocks)) + return -EINVAL; /* not enough space */ + + profile = aa_current_profile(); + + /* We are going to leave space for two numbers. The first is the total + * number of bytes we are writing after the first number. This is so + * users can read the full output without reallocation. + * + * The second number is the number of data blocks we're writing. An + * application might be confined by multiple policies having data in + * the same key. + */ + memset(buf, 0, sizeof(bytes) + sizeof(blocks)); + out = buf + sizeof(bytes) + sizeof(blocks); + + blocks = 0; + if (profile->data) { + data = rhashtable_lookup_fast(profile->data, &key, + profile->data->p); + + if (data) { + if (out + sizeof(outle32) + data->size > buf + buf_len) + return -EINVAL; /* not enough space */ + outle32 = __cpu_to_le32(data->size); + memcpy(out, &outle32, sizeof(outle32)); + out += sizeof(outle32); + memcpy(out, data->data, data->size); + out += data->size; + blocks++; + } + } + + outle32 = __cpu_to_le32(out - buf - sizeof(bytes)); + memcpy(buf, &outle32, sizeof(outle32)); + outle32 = __cpu_to_le32(blocks); + memcpy(buf + sizeof(bytes), &outle32, sizeof(outle32)); + + return out - buf; +} + +#define QUERY_CMD_DATA "data\0" +#define QUERY_CMD_DATA_LEN 5 + +/** + * aa_write_access - generic permissions and data query + * @file: pointer to open apparmorfs/access file + * @ubuf: user buffer containing the complete query string (NOT NULL) + * @count: size of ubuf + * @ppos: position in the file (MUST BE ZERO) + * + * Allows for one permissions or data query per open(), write(), and read() + * sequence. The only queries currently supported are label-based queries for + * permissions or data. + * + * For permissions queries, ubuf must begin with "label\0", followed by the + * profile query specific format described in the query_label() function + * documentation. + * + * For data queries, ubuf must have the form "data\0<LABEL>\0<KEY>\0", where + * <LABEL> is the name of the security confinement context and <KEY> is the + * name of the data to retrieve. + * + * Returns: number of bytes written or -errno on failure + */ +static ssize_t aa_write_access(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + char *buf; + ssize_t len; + + if (*ppos) + return -ESPIPE; + + buf = simple_transaction_get(file, ubuf, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + if (count > QUERY_CMD_DATA_LEN && + !memcmp(buf, QUERY_CMD_DATA, QUERY_CMD_DATA_LEN)) { + len = query_data(buf, SIMPLE_TRANSACTION_LIMIT, + buf + QUERY_CMD_DATA_LEN, + count - QUERY_CMD_DATA_LEN); + } else + len = -EINVAL; + + if (len < 0) + return len; + + simple_transaction_set(file, len); + + return count; +} + +static const struct file_operations aa_fs_access = { + .write = aa_write_access, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + static int aa_fs_seq_show(struct seq_file *seq, void *v) { struct aa_fs_entry *fs_file = seq->private; @@ -227,12 +392,12 @@ const struct file_operations aa_fs_seq_file_ops = { static int aa_fs_seq_profile_open(struct inode *inode, struct file *file, int (*show)(struct seq_file *, void *)) { - struct aa_replacedby *r = aa_get_replacedby(inode->i_private); - int error = single_open(file, show, r); + struct aa_proxy *proxy = aa_get_proxy(inode->i_private); + int error = single_open(file, show, proxy); if (error) { file->private_data = NULL; - aa_put_replacedby(r); + aa_put_proxy(proxy); } return error; @@ -242,14 +407,14 @@ static int aa_fs_seq_profile_release(struct inode *inode, struct file *file) { struct seq_file *seq = (struct seq_file *) file->private_data; if (seq) - aa_put_replacedby(seq->private); + aa_put_proxy(seq->private); return single_release(inode, file); } static int aa_fs_seq_profname_show(struct seq_file *seq, void *v) { - struct aa_replacedby *r = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + struct aa_proxy *proxy = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); seq_printf(seq, "%s\n", profile->base.name); aa_put_profile(profile); @@ -271,8 +436,8 @@ static const struct file_operations aa_fs_profname_fops = { static int aa_fs_seq_profmode_show(struct seq_file *seq, void *v) { - struct aa_replacedby *r = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + struct aa_proxy *proxy = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); seq_printf(seq, "%s\n", aa_profile_mode_names[profile->mode]); aa_put_profile(profile); @@ -294,8 +459,8 @@ static const struct file_operations aa_fs_profmode_fops = { static int aa_fs_seq_profattach_show(struct seq_file *seq, void *v) { - struct aa_replacedby *r = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + struct aa_proxy *proxy = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); if (profile->attach) seq_printf(seq, "%s\n", profile->attach); else if (profile->xmatch) @@ -322,8 +487,8 @@ static const struct file_operations aa_fs_profattach_fops = { static int aa_fs_seq_hash_show(struct seq_file *seq, void *v) { - struct aa_replacedby *r = seq->private; - struct aa_profile *profile = aa_get_profile_rcu(&r->profile); + struct aa_proxy *proxy = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); unsigned int i, size = aa_hash_size(); if (profile->hash) { @@ -349,6 +514,145 @@ static const struct file_operations aa_fs_seq_hash_fops = { .release = single_release, }; + +static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v) +{ + struct aa_ns *ns = aa_current_profile()->ns; + + seq_printf(seq, "%d\n", ns->level); + + return 0; +} + +static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file) +{ + return single_open(file, aa_fs_seq_show_ns_level, inode->i_private); +} + +static const struct file_operations aa_fs_ns_level = { + .owner = THIS_MODULE, + .open = aa_fs_seq_open_ns_level, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v) +{ + struct aa_ns *ns = aa_current_profile()->ns; + + seq_printf(seq, "%s\n", ns->base.name); + + return 0; +} + +static int aa_fs_seq_open_ns_name(struct inode *inode, struct file *file) +{ + return single_open(file, aa_fs_seq_show_ns_name, inode->i_private); +} + +static const struct file_operations aa_fs_ns_name = { + .owner = THIS_MODULE, + .open = aa_fs_seq_open_ns_name, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int rawdata_release(struct inode *inode, struct file *file) +{ + /* TODO: switch to loaddata when profile switched to symlink */ + aa_put_loaddata(file->p |