Skip to content

Commit

Permalink
apparmor: allow introspecting the loaded policy pre internal transform
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
John Johansen committed Jan 16, 2017
1 parent fc1c9fd commit 5ac8c35
Show file tree
Hide file tree
Showing 8 changed files with 278 additions and 58 deletions.
213 changes: 172 additions & 41 deletions security/apparmor/apparmorfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -84,58 +85,69 @@ 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);

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(__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);
}

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,
Expand All @@ -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;
}
Expand All @@ -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;
}

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
}

Expand Down
39 changes: 38 additions & 1 deletion security/apparmor/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions security/apparmor/include/apparmorfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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])
Expand Down
5 changes: 5 additions & 0 deletions security/apparmor/include/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Loading

0 comments on commit 5ac8c35

Please sign in to comment.