diff options
-rw-r--r-- | security/apparmor/apparmorfs.c | 213 | ||||
-rw-r--r-- | security/apparmor/crypto.c | 39 | ||||
-rw-r--r-- | security/apparmor/include/apparmorfs.h | 5 | ||||
-rw-r--r-- | security/apparmor/include/crypto.h | 5 | ||||
-rw-r--r-- | security/apparmor/include/policy.h | 5 | ||||
-rw-r--r-- | security/apparmor/include/policy_unpack.h | 27 | ||||
-rw-r--r-- | security/apparmor/policy.c | 14 | ||||
-rw-r--r-- | security/apparmor/policy_unpack.c | 28 |
8 files changed, 278 insertions, 58 deletions
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index cc6ee1ee2b42..2e6790cf54da 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -33,6 +33,7 @@ #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 @@ -84,11 +85,13 @@ static int mangle_name(const 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(int op, + 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); @@ -96,19 +99,16 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf, /* 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(__aa_current_profile(), NULL, 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); } @@ -116,26 +116,38 @@ 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) { - char *data; ssize_t error; + struct aa_loaddata *data; + struct aa_profile *profile = aa_current_profile(); + int 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, 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(op, buf, size, size, pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - error = aa_replace_profiles(__aa_current_profile()->ns, data, - size, PROF_ADD); - kvfree(data); + error = aa_replace_profiles(profile->ns, binop, data); + aa_put_loaddata(data); } return error; } +static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, + loff_t *pos) +{ + int error = policy_update(PROF_ADD, buf, size, pos); + + return error; +} + static const struct file_operations aa_fs_profile_load = { .write = profile_load, .llseek = default_llseek, @@ -145,16 +157,7 @@ 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; - - 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(__aa_current_profile()->ns, data, - size, PROF_REPLACE); - kvfree(data); - } + int error = policy_update(PROF_REPLACE, buf, size, pos); return error; } @@ -164,27 +167,35 @@ static const struct file_operations aa_fs_profile_replace = { .llseek = default_llseek, }; -/* .remove file hook fn to remove loaded policy */ 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; + profile = aa_current_profile(); + /* high level check about policy management - fine grained in + * below after unpack + */ + error = aa_may_manage_policy(profile, 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(OP_PROF_RM, buf, size + 1, size, + pos); error = PTR_ERR(data); if (!IS_ERR(data)) { - data[size] = 0; - error = aa_remove_profiles(__aa_current_profile()->ns, data, - size); - kvfree(data); + data->data[size] = 0; + error = aa_remove_profiles(profile->ns, data->data, size); + aa_put_loaddata(data); } - + out: return error; } @@ -401,6 +412,100 @@ static const struct file_operations aa_fs_ns_name = { .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->private_data); + + return 0; +} + +static int aa_fs_seq_raw_abi_show(struct seq_file *seq, void *v) +{ + struct aa_proxy *proxy = seq->private; + struct aa_profile *profile = aa_get_profile_rcu(&proxy->profile); + + if (profile->rawdata->abi) { + seq_printf(seq, "v%d", profile->rawdata->abi); + seq_puts(seq, "\n"); + } + aa_put_profile(profile); + + return 0; +} + +static int aa_fs_seq_raw_abi_open(struct inode *inode, struct file *file) +{ + return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_abi_show); +} + +static const struct file_operations aa_fs_seq_raw_abi_fops = { + .owner = THIS_MODULE, + .open = aa_fs_seq_raw_abi_open, + .read = seq_read, + .llseek = seq_lseek, + .release = aa_fs_seq_profile_release, +}; + +static int aa_fs_seq_raw_hash_show(struct seq_file *seq, void *v) +{ + 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->rawdata->hash) { + for (i = 0; i < size; i++) + seq_printf(seq, "%.2x", profile->rawdata->hash[i]); + seq_puts(seq, "\n"); + } + aa_put_profile(profile); + + return 0; +} + +static int aa_fs_seq_raw_hash_open(struct inode *inode, struct file *file) +{ + return aa_fs_seq_profile_open(inode, file, aa_fs_seq_raw_hash_show); +} + +static const struct file_operations aa_fs_seq_raw_hash_fops = { + .owner = THIS_MODULE, + .open = aa_fs_seq_raw_hash_open, + .read = seq_read, + .llseek = seq_lseek, + .release = aa_fs_seq_profile_release, +}; + +static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, + loff_t *ppos) +{ + struct aa_loaddata *rawdata = file->private_data; + + return simple_read_from_buffer(buf, size, ppos, rawdata->data, + rawdata->size); +} + +static int rawdata_open(struct inode *inode, struct file *file) +{ + struct aa_proxy *proxy = inode->i_private; + struct aa_profile *profile; + + if (!policy_view_capable(NULL)) + return -EACCES; + profile = aa_get_profile_rcu(&proxy->profile); + file->private_data = aa_get_loaddata(profile->rawdata); + aa_put_profile(profile); + + return 0; +} + +static const struct file_operations aa_fs_rawdata_fops = { + .open = rawdata_open, + .read = rawdata_read, + .llseek = generic_file_llseek, + .release = rawdata_release, +}; + /** fns to setup dynamic per profile/namespace files **/ void __aa_fs_profile_rmdir(struct aa_profile *profile) { @@ -512,6 +617,29 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) profile->dents[AAFS_PROF_HASH] = dent; } + if (profile->rawdata) { + dent = create_profile_file(dir, "raw_sha1", profile, + &aa_fs_seq_raw_hash_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_RAW_HASH] = dent; + + dent = create_profile_file(dir, "raw_abi", profile, + &aa_fs_seq_raw_abi_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_RAW_ABI] = dent; + + dent = securityfs_create_file("raw_data", S_IFREG | 0444, dir, + profile->proxy, + &aa_fs_rawdata_fops); + if (IS_ERR(dent)) + goto fail; + profile->dents[AAFS_PROF_RAW_DATA] = dent; + d_inode(dent)->i_size = profile->rawdata->size; + aa_get_proxy(profile->proxy); + } + list_for_each_entry(child, &profile->base.profiles, base.list) { error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); if (error) @@ -817,6 +945,9 @@ static const struct seq_operations aa_fs_profiles_op = { static int profiles_open(struct inode *inode, struct file *file) { + if (!policy_view_capable(NULL)) + return -EACCES; + return seq_open(file, &aa_fs_profiles_op); } diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c index b75dab0df1cb..de8dc78b6144 100644 --- a/security/apparmor/crypto.c +++ b/security/apparmor/crypto.c @@ -29,6 +29,43 @@ unsigned int aa_hash_size(void) return apparmor_hash_size; } +char *aa_calc_hash(void *data, size_t len) +{ + struct { + struct shash_desc shash; + char ctx[crypto_shash_descsize(apparmor_tfm)]; + } desc; + char *hash = NULL; + int error = -ENOMEM; + + if (!apparmor_tfm) + return NULL; + + hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + if (!hash) + goto fail; + + desc.shash.tfm = apparmor_tfm; + desc.shash.flags = 0; + + error = crypto_shash_init(&desc.shash); + if (error) + goto fail; + error = crypto_shash_update(&desc.shash, (u8 *) data, len); + if (error) + goto fail; + error = crypto_shash_final(&desc.shash, hash); + if (error) + goto fail; + + return hash; + +fail: + kfree(hash); + + return ERR_PTR(error); +} + int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len) { @@ -37,7 +74,7 @@ int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, char ctx[crypto_shash_descsize(apparmor_tfm)]; } desc; int error = -ENOMEM; - u32 le32_version = cpu_to_le32(version); + __le32 le32_version = cpu_to_le32(version); if (!aa_g_hash_policy) return 0; diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index eeeae5b0cc36..a593e75b3b03 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -70,6 +70,7 @@ enum aafs_ns_type { AAFS_NS_DIR, AAFS_NS_PROFS, AAFS_NS_NS, + AAFS_NS_RAW_DATA, AAFS_NS_COUNT, AAFS_NS_MAX_COUNT, AAFS_NS_SIZE, @@ -85,12 +86,16 @@ enum aafs_prof_type { AAFS_PROF_MODE, AAFS_PROF_ATTACH, AAFS_PROF_HASH, + AAFS_PROF_RAW_DATA, + AAFS_PROF_RAW_HASH, + AAFS_PROF_RAW_ABI, AAFS_PROF_SIZEOF, }; #define ns_dir(X) ((X)->dents[AAFS_NS_DIR]) #define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS]) #define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS]) +#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA]) #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h index dc418e5024d9..c1469f8db174 100644 --- a/security/apparmor/include/crypto.h +++ b/security/apparmor/include/crypto.h @@ -18,9 +18,14 @@ #ifdef CONFIG_SECURITY_APPARMOR_HASH unsigned int aa_hash_size(void); +char *aa_calc_hash(void *data, size_t len); int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len); #else +static inline char *aa_calc_hash(void *data, size_t len) +{ + return NULL; +} static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len) { diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 95641e235d47..fbbc8677f527 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -161,6 +161,7 @@ struct aa_profile { struct aa_caps caps; struct aa_rlimit rlimits; + struct aa_loaddata *rawdata; unsigned char *hash; char *dirname; struct dentry *dents[AAFS_PROF_SIZEOF]; @@ -187,8 +188,8 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_profile *base, const char *fqname, size_t n); struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); -ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size, - bool noreplace); +ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace, + struct aa_loaddata *udata); ssize_t aa_remove_profiles(struct aa_ns *view, char *name, size_t size); void __aa_profile_list_release(struct list_head *head); diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index c214fb88b1bc..7b675b6f7f02 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -16,6 +16,7 @@ #define __POLICY_INTERFACE_H #include <linux/list.h> +#include <linux/kref.h> struct aa_load_ent { struct list_head list; @@ -34,6 +35,30 @@ struct aa_load_ent *aa_load_ent_alloc(void); #define PACKED_MODE_KILL 2 #define PACKED_MODE_UNCONFINED 3 -int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns); +/* struct aa_loaddata - buffer of policy load data set */ +struct aa_loaddata { + struct kref count; + size_t size; + int abi; + unsigned char *hash; + char data[]; +}; + +int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); + +static inline struct aa_loaddata * +aa_get_loaddata(struct aa_loaddata *data) +{ + if (data) + kref_get(&(data->count)); + return data; +} + +void aa_loaddata_kref(struct kref *kref); +static inline void aa_put_loaddata(struct aa_loaddata *data) +{ + if (data) + kref_put(&data->count, aa_loaddata_kref); +} #endif /* __POLICY_INTERFACE_H */ diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 3c5c0b28eac5..ff29b606f2b3 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -228,6 +228,7 @@ void aa_free_profile(struct aa_profile *profile) aa_put_proxy(profile->proxy); kzfree(profile->hash); + aa_put_loaddata(profile->rawdata); kzfree(profile); } @@ -802,10 +803,8 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, /** * aa_replace_profiles - replace profile(s) on the profile list * @view: namespace load is viewed from - * @profile: profile that is attempting to load/replace policy - * @udata: serialized data stream (NOT NULL) - * @size: size of the serialized data stream * @noreplace: true if only doing addition, no replacement allowed + * @udata: serialized data stream (NOT NULL) * * unpack and replace a profile on the profile list and uses of that profile * by any aa_task_cxt. If the profile does not exist on the profile list @@ -813,8 +812,8 @@ static int __lookup_replace(struct aa_ns *ns, const char *hname, * * Returns: size of data consumed else error code on failure. */ -ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size, - bool noreplace) +ssize_t aa_replace_profiles(struct aa_ns *view, bool noreplace, + struct aa_loaddata *udata) { const char *ns_name, *info = NULL; struct aa_ns *ns = NULL; @@ -824,7 +823,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size, LIST_HEAD(lh); /* released below */ - error = aa_unpack(udata, size, &lh, &ns_name); + error = aa_unpack(udata, &lh, &ns_name); if (error) goto out; @@ -841,6 +840,7 @@ ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size, /* setup parent and ns info */ list_for_each_entry(ent, &lh, list) { struct aa_policy *policy; + ent->new->rawdata = aa_get_loaddata(udata); error = __lookup_replace(ns, ent->new->base.hname, noreplace, &ent->old, &info); if (error) @@ -957,7 +957,7 @@ out: if (error) return error; - return size; + return udata->size; fail_lock: mutex_unlock(&ns->lock); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 51a7f9fc8a3e..fb4ef84b88e1 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -117,6 +117,16 @@ static int audit_iface(struct aa_profile *new, const char *name, audit_cb); } +void aa_loaddata_kref(struct kref *kref) +{ + struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); + + if (d) { + kzfree(d->hash); + kvfree(d); + } +} + /* test if read will be in packed data bounds */ static bool inbounds(struct aa_ext *e, size_t size) { @@ -749,7 +759,6 @@ struct aa_load_ent *aa_load_ent_alloc(void) /** * aa_unpack - unpack packed binary profile(s) data loaded from user space * @udata: user data copied to kmem (NOT NULL) - * @size: the size of the user data * @lh: list to place unpacked profiles in a aa_repl_ws * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) * @@ -759,15 +768,16 @@ struct aa_load_ent *aa_load_ent_alloc(void) * * Returns: profile(s) on @lh else error pointer if fails to unpack */ -int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) +int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, + const char **ns) { struct aa_load_ent *tmp, *ent; struct aa_profile *profile = NULL; int error; struct aa_ext e = { - .start = udata, - .end = udata + size, - .pos = udata, + .start = udata->data, + .end = udata->data + udata->size, + .pos = udata->data, }; *ns = NULL; @@ -802,7 +812,13 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) ent->new = profile; list_add_tail(&ent->list, lh); } - + udata->abi = e.version & K_ABI_MASK; + udata->hash = aa_calc_hash(udata->data, udata->size); + if (IS_ERR(udata->hash)) { + error = PTR_ERR(udata->hash); + udata->hash = NULL; + goto fail; + } return 0; fail_profile: |