apparmor: allow introspecting the loaded policy pre internal transform

Store loaded policy and allow introspecting it through apparmorfs. This
has several uses from debugging, policy validation, and policy checkpoint
and restore for containers.

Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
John Johansen 2017-01-16 00:42:55 -08:00
parent fc1c9fd10a
commit 5ac8c355ae
8 changed files with 280 additions and 60 deletions

View file

@ -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,22 +116,34 @@ static char *aa_simple_write_to_buffer(int op, const char __user *userbuf,
return data;
}
static ssize_t policy_update(int binop, const char __user *buf, size_t size,
loff_t *pos)
{
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, buf, size, size, pos);
error = PTR_ERR(data);
if (!IS_ERR(data)) {
error = aa_replace_profiles(profile->ns, 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)
{
char *data;
ssize_t error;
data = aa_simple_write_to_buffer(OP_PROF_LOAD, 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);
}
int error = policy_update(PROF_ADD, buf, size, pos);
return error;
}
@ -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);
}

View file

@ -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;

View file

@ -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])

View file

@ -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)
{

View file

@ -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);

View file

@ -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 */

View file

@ -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 @@ ssize_t aa_replace_profiles(struct aa_ns *view, void *udata, size_t size,
if (error)
return error;
return size;
return udata->size;
fail_lock:
mutex_unlock(&ns->lock);

View file

@ -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: