aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--security/apparmor/apparmorfs.c213
-rw-r--r--security/apparmor/crypto.c39
-rw-r--r--security/apparmor/include/apparmorfs.h5
-rw-r--r--security/apparmor/include/crypto.h5
-rw-r--r--security/apparmor/include/policy.h5
-rw-r--r--security/apparmor/include/policy_unpack.h27
-rw-r--r--security/apparmor/policy.c14
-rw-r--r--security/apparmor/policy_unpack.c28
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: