apparmor: support querying extended trusted helper extra data
Allow a profile to carry extra data that can be queried via userspace. This provides a means to store extra data in a profile that a trusted helper can extract and use from live policy. Signed-off-by: William Hua <william.hua@canonical.com> Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
parent
12eb87d50b
commit
e025be0f26
5 changed files with 245 additions and 0 deletions
|
@ -213,6 +213,144 @@ static const struct file_operations aa_fs_profile_remove = {
|
||||||
.llseek = default_llseek,
|
.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)
|
static int aa_fs_seq_show(struct seq_file *seq, void *v)
|
||||||
{
|
{
|
||||||
struct aa_fs_entry *fs_file = seq->private;
|
struct aa_fs_entry *fs_file = seq->private;
|
||||||
|
@ -1078,6 +1216,7 @@ static struct aa_fs_entry aa_fs_entry_features[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct aa_fs_entry aa_fs_entry_apparmor[] = {
|
static struct aa_fs_entry aa_fs_entry_apparmor[] = {
|
||||||
|
AA_FS_FILE_FOPS(".access", 0640, &aa_fs_access),
|
||||||
AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level),
|
AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level),
|
||||||
AA_FS_FILE_FOPS(".ns_name", 0640, &aa_fs_ns_name),
|
AA_FS_FILE_FOPS(".ns_name", 0640, &aa_fs_ns_name),
|
||||||
AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops),
|
AA_FS_FILE_FOPS("profiles", 0440, &aa_fs_profiles_fops),
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include <linux/capability.h>
|
#include <linux/capability.h>
|
||||||
#include <linux/cred.h>
|
#include <linux/cred.h>
|
||||||
#include <linux/kref.h>
|
#include <linux/kref.h>
|
||||||
|
#include <linux/rhashtable.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/slab.h>
|
#include <linux/slab.h>
|
||||||
#include <linux/socket.h>
|
#include <linux/socket.h>
|
||||||
|
@ -98,6 +99,19 @@ struct aa_proxy {
|
||||||
struct aa_profile __rcu *profile;
|
struct aa_profile __rcu *profile;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* struct aa_data - generic data structure
|
||||||
|
* key: name for retrieving this data
|
||||||
|
* size: size of data in bytes
|
||||||
|
* data: binary data
|
||||||
|
* head: reserved for rhashtable
|
||||||
|
*/
|
||||||
|
struct aa_data {
|
||||||
|
char *key;
|
||||||
|
u32 size;
|
||||||
|
char *data;
|
||||||
|
struct rhash_head head;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/* struct aa_profile - basic confinement data
|
/* struct aa_profile - basic confinement data
|
||||||
* @base - base components of the profile (name, refcount, lists, lock ...)
|
* @base - base components of the profile (name, refcount, lists, lock ...)
|
||||||
|
@ -122,6 +136,7 @@ struct aa_proxy {
|
||||||
*
|
*
|
||||||
* @dents: dentries for the profiles file entries in apparmorfs
|
* @dents: dentries for the profiles file entries in apparmorfs
|
||||||
* @dirname: name of the profile dir in apparmorfs
|
* @dirname: name of the profile dir in apparmorfs
|
||||||
|
* @data: hashtable for free-form policy aa_data
|
||||||
*
|
*
|
||||||
* The AppArmor profile contains the basic confinement data. Each profile
|
* The AppArmor profile contains the basic confinement data. Each profile
|
||||||
* has a name, and exists in a namespace. The @name and @exec_match are
|
* has a name, and exists in a namespace. The @name and @exec_match are
|
||||||
|
@ -165,6 +180,7 @@ struct aa_profile {
|
||||||
unsigned char *hash;
|
unsigned char *hash;
|
||||||
char *dirname;
|
char *dirname;
|
||||||
struct dentry *dents[AAFS_PROF_SIZEOF];
|
struct dentry *dents[AAFS_PROF_SIZEOF];
|
||||||
|
struct rhashtable *data;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern enum profile_mode aa_g_profile_mode;
|
extern enum profile_mode aa_g_profile_mode;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include <linux/sysctl.h>
|
#include <linux/sysctl.h>
|
||||||
#include <linux/audit.h>
|
#include <linux/audit.h>
|
||||||
#include <linux/user_namespace.h>
|
#include <linux/user_namespace.h>
|
||||||
|
#include <linux/kmemleak.h>
|
||||||
#include <net/sock.h>
|
#include <net/sock.h>
|
||||||
|
|
||||||
#include "include/apparmor.h"
|
#include "include/apparmor.h"
|
||||||
|
|
|
@ -194,6 +194,20 @@ void aa_free_proxy_kref(struct kref *kref)
|
||||||
free_proxy(p);
|
free_proxy(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* aa_free_data - free a data blob
|
||||||
|
* @ptr: data to free
|
||||||
|
* @arg: unused
|
||||||
|
*/
|
||||||
|
static void aa_free_data(void *ptr, void *arg)
|
||||||
|
{
|
||||||
|
struct aa_data *data = ptr;
|
||||||
|
|
||||||
|
kzfree(data->data);
|
||||||
|
kzfree(data->key);
|
||||||
|
kzfree(data);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aa_free_profile - free a profile
|
* aa_free_profile - free a profile
|
||||||
* @profile: the profile to free (MAYBE NULL)
|
* @profile: the profile to free (MAYBE NULL)
|
||||||
|
@ -206,6 +220,8 @@ void aa_free_proxy_kref(struct kref *kref)
|
||||||
*/
|
*/
|
||||||
void aa_free_profile(struct aa_profile *profile)
|
void aa_free_profile(struct aa_profile *profile)
|
||||||
{
|
{
|
||||||
|
struct rhashtable *rht;
|
||||||
|
|
||||||
AA_DEBUG("%s(%p)\n", __func__, profile);
|
AA_DEBUG("%s(%p)\n", __func__, profile);
|
||||||
|
|
||||||
if (!profile)
|
if (!profile)
|
||||||
|
@ -227,6 +243,13 @@ void aa_free_profile(struct aa_profile *profile)
|
||||||
aa_put_dfa(profile->policy.dfa);
|
aa_put_dfa(profile->policy.dfa);
|
||||||
aa_put_proxy(profile->proxy);
|
aa_put_proxy(profile->proxy);
|
||||||
|
|
||||||
|
if (profile->data) {
|
||||||
|
rht = profile->data;
|
||||||
|
profile->data = NULL;
|
||||||
|
rhashtable_free_and_destroy(rht, aa_free_data, NULL);
|
||||||
|
kzfree(rht);
|
||||||
|
}
|
||||||
|
|
||||||
kzfree(profile->hash);
|
kzfree(profile->hash);
|
||||||
aa_put_loaddata(profile->rawdata);
|
aa_put_loaddata(profile->rawdata);
|
||||||
kzfree(profile);
|
kzfree(profile);
|
||||||
|
|
|
@ -485,6 +485,30 @@ fail:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *kvmemdup(const void *src, size_t len)
|
||||||
|
{
|
||||||
|
void *p = kvmalloc(len);
|
||||||
|
|
||||||
|
if (p)
|
||||||
|
memcpy(p, src, len);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 strhash(const void *data, u32 len, u32 seed)
|
||||||
|
{
|
||||||
|
const char * const *key = data;
|
||||||
|
|
||||||
|
return jhash(*key, strlen(*key), seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int datacmp(struct rhashtable_compare_arg *arg, const void *obj)
|
||||||
|
{
|
||||||
|
const struct aa_data *data = obj;
|
||||||
|
const char * const *key = arg->key;
|
||||||
|
|
||||||
|
return strcmp(data->key, *key);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unpack_profile - unpack a serialized profile
|
* unpack_profile - unpack a serialized profile
|
||||||
* @e: serialized data extent information (NOT NULL)
|
* @e: serialized data extent information (NOT NULL)
|
||||||
|
@ -496,6 +520,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||||
struct aa_profile *profile = NULL;
|
struct aa_profile *profile = NULL;
|
||||||
const char *tmpname, *tmpns = NULL, *name = NULL;
|
const char *tmpname, *tmpns = NULL, *name = NULL;
|
||||||
size_t ns_len;
|
size_t ns_len;
|
||||||
|
struct rhashtable_params params = { 0 };
|
||||||
|
char *key = NULL;
|
||||||
|
struct aa_data *data;
|
||||||
int i, error = -EPROTO;
|
int i, error = -EPROTO;
|
||||||
kernel_cap_t tmpcap;
|
kernel_cap_t tmpcap;
|
||||||
u32 tmp;
|
u32 tmp;
|
||||||
|
@ -654,6 +681,45 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||||
if (!unpack_trans_table(e, profile))
|
if (!unpack_trans_table(e, profile))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
if (unpack_nameX(e, AA_STRUCT, "data")) {
|
||||||
|
profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
|
||||||
|
if (!profile->data)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
params.nelem_hint = 3;
|
||||||
|
params.key_len = sizeof(void *);
|
||||||
|
params.key_offset = offsetof(struct aa_data, key);
|
||||||
|
params.head_offset = offsetof(struct aa_data, head);
|
||||||
|
params.hashfn = strhash;
|
||||||
|
params.obj_cmpfn = datacmp;
|
||||||
|
|
||||||
|
if (rhashtable_init(profile->data, ¶ms))
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
while (unpack_strdup(e, &key, NULL)) {
|
||||||
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||||
|
if (!data) {
|
||||||
|
kzfree(key);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->key = key;
|
||||||
|
data->size = unpack_blob(e, &data->data, NULL);
|
||||||
|
data->data = kvmemdup(data->data, data->size);
|
||||||
|
if (data->size && !data->data) {
|
||||||
|
kzfree(data->key);
|
||||||
|
kzfree(data);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
rhashtable_insert_fast(profile->data, &data->head,
|
||||||
|
profile->data->p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue