Commit 4125e8e9 authored by John Johansen's avatar John Johansen Committed by Seth Forshee

UBUNTU: SAUCE: (no-up) apparmor: sync of apparmor 3.6 (from Ubuntu 16.10)

This is a backport of apparmor 3.6 that is in 16.10 to support
lxc/lxd containers.

BugLink: http://bugs.launchpad.net/bugs/1611078Signed-off-by: default avatarJohn Johansen <john.johansen@canonical.com>
Acked-by: default avatarBrad Figg <brad.figg@canonical.com>
Signed-off-by: default avatarTim Gardner <tim.gardner@canonical.com>
parent bea2dd12
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <linux/err.h> #include <linux/err.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/fs.h>
struct linux_binprm; struct linux_binprm;
struct cred; struct cred;
...@@ -1585,7 +1586,15 @@ static inline void security_audit_rule_free(void *lsmrule) ...@@ -1585,7 +1586,15 @@ static inline void security_audit_rule_free(void *lsmrule)
#endif /* CONFIG_AUDIT */ #endif /* CONFIG_AUDIT */
#ifdef CONFIG_SECURITYFS #ifdef CONFIG_SECURITYFS
extern int securityfs_pin_fs(void);
extern int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry,
umode_t mode, void *data,
const struct file_operations *fops,
const struct inode_operations *iops);
extern struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops,
const struct inode_operations *iops);
extern struct dentry *securityfs_create_file(const char *name, umode_t mode, extern struct dentry *securityfs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data, struct dentry *parent, void *data,
const struct file_operations *fops); const struct file_operations *fops);
...@@ -1593,6 +1602,28 @@ extern struct dentry *securityfs_create_dir(const char *name, struct dentry *par ...@@ -1593,6 +1602,28 @@ extern struct dentry *securityfs_create_dir(const char *name, struct dentry *par
extern void securityfs_remove(struct dentry *dentry); extern void securityfs_remove(struct dentry *dentry);
#else /* CONFIG_SECURITYFS */ #else /* CONFIG_SECURITYFS */
static inline int securityfs_pin_fs(void)
{
return -ENODEV;
}
static inline int __securityfs_setup_d_inode(struct inode *dir,
struct dentry *dentry,
umode_t mode, void *data,
const struct file_operations *fops,
const struct inode_operations *iops)
{
return -ENODEV;
}
static inline struct dentry *securityfs_create_dentry(const char *name,
umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops,
const struct inode_operations *iops)
{
return ERR_PTR(-ENODEV);
}
static inline struct dentry *securityfs_create_dir(const char *name, static inline struct dentry *securityfs_create_dir(const char *name,
struct dentry *parent) struct dentry *parent)
......
...@@ -66,7 +66,7 @@ config SECURITY_APPARMOR_UNCONFINED_INIT ...@@ -66,7 +66,7 @@ config SECURITY_APPARMOR_UNCONFINED_INIT
If you are unsure how to answer this question, answer Y. If you are unsure how to answer this question, answer Y.
config SECURITY_APPARMOR_HASH config SECURITY_APPARMOR_HASH
bool "enable introspection of sha1 hashes for loaded profiles" bool "Enable introspection of sha1 hashes for loaded profiles"
depends on SECURITY_APPARMOR depends on SECURITY_APPARMOR
select CRYPTO select CRYPTO
select CRYPTO_SHA1 select CRYPTO_SHA1
......
...@@ -5,7 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o ...@@ -5,7 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \ apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
resource.o sid.o file.o label.o mount.o net.o af_unix.o \ resource.o sid.o file.o label.o mount.o net.o af_unix.o \
policy_ns.o policy_ns.o backport.o
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
clean-files := capability_names.h rlim_names.h net_names.h clean-files := capability_names.h rlim_names.h net_names.h
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include <linux/capability.h> #include <linux/capability.h>
#include <linux/rcupdate.h> #include <linux/rcupdate.h>
#include <uapi/linux/major.h> #include <uapi/linux/major.h>
#include <linux/fs.h>
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/apparmorfs.h" #include "include/apparmorfs.h"
...@@ -34,7 +35,9 @@ ...@@ -34,7 +35,9 @@
#include "include/label.h" #include "include/label.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/resource.h" #include "include/resource.h"
#include "include/label.h"
#include "include/lib.h" #include "include/lib.h"
#include "include/policy_unpack.h"
/** /**
* aa_mangle_name - mangle a profile name to std profile layout form * aa_mangle_name - mangle a profile name to std profile layout form
...@@ -85,11 +88,12 @@ static int mangle_name(const char *name, char *target) ...@@ -85,11 +88,12 @@ static int mangle_name(const char *name, char *target)
* Returns: kernel buffer containing copy of user buffer data or an * Returns: kernel buffer containing copy of user buffer data or an
* ERR_PTR on failure. * ERR_PTR on failure.
*/ */
static char *aa_simple_write_to_buffer(const char __user *userbuf, static struct aa_loaddata *aa_simple_write_to_buffer(const char __user *userbuf,
size_t alloc_size, size_t copy_size, size_t alloc_size,
size_t copy_size,
loff_t *pos) loff_t *pos)
{ {
char *data; struct aa_loaddata *data;
BUG_ON(copy_size > alloc_size); BUG_ON(copy_size > alloc_size);
...@@ -98,11 +102,15 @@ static char *aa_simple_write_to_buffer(const char __user *userbuf, ...@@ -98,11 +102,15 @@ static char *aa_simple_write_to_buffer(const char __user *userbuf,
return ERR_PTR(-ESPIPE); return ERR_PTR(-ESPIPE);
/* freed by caller to simple_write_to_buffer */ /* freed by caller to simple_write_to_buffer */
data = kvmalloc(alloc_size); data = kvmalloc(sizeof(*data) + alloc_size);
if (data == NULL) if (data == NULL)
return ERR_PTR(-ENOMEM); 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); kvfree(data);
return ERR_PTR(-EFAULT); return ERR_PTR(-EFAULT);
} }
...@@ -111,26 +119,27 @@ static char *aa_simple_write_to_buffer(const char __user *userbuf, ...@@ -111,26 +119,27 @@ static char *aa_simple_write_to_buffer(const char __user *userbuf,
} }
static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
loff_t *pos) loff_t *pos, struct aa_ns *ns)
{ {
struct aa_label *label; struct aa_label *label;
ssize_t error; ssize_t error;
char *data; struct aa_loaddata *data;
label = aa_begin_current_label(DO_UPDATE); label = aa_begin_current_label(DO_UPDATE);
/* high level check about policy management - fine grained in /* high level check about policy management - fine grained in
* below after unpack * below after unpack
*/ */
error = aa_may_manage_policy(label, mask); error = aa_may_manage_policy(label, ns, mask);
if (error) if (error)
return error; return error;
data = aa_simple_write_to_buffer(buf, size, size, pos); data = aa_simple_write_to_buffer(buf, size, size, pos);
error = PTR_ERR(data); error = PTR_ERR(data);
if (!IS_ERR(data)) { if (!IS_ERR(data)) {
error = aa_replace_profiles(label, mask, data, size); error = aa_replace_profiles(ns ? ns : labels_ns(label), label,
kvfree(data); mask, data);
aa_put_loaddata(data);
} }
aa_end_current_label(label); aa_end_current_label(label);
...@@ -141,7 +150,11 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size, ...@@ -141,7 +150,11 @@ static ssize_t policy_update(u32 mask, const char __user *buf, size_t size,
static ssize_t profile_load(struct file *f, const char __user *buf, size_t size, static ssize_t profile_load(struct file *f, const char __user *buf, size_t size,
loff_t *pos) loff_t *pos)
{ {
return policy_update(AA_MAY_LOAD_POLICY, buf, size, pos); struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
int error = policy_update(AA_MAY_LOAD_POLICY, buf, size, pos, ns);
aa_put_ns(ns);
return error;
} }
static const struct file_operations aa_fs_profile_load = { static const struct file_operations aa_fs_profile_load = {
...@@ -153,8 +166,12 @@ static const struct file_operations aa_fs_profile_load = { ...@@ -153,8 +166,12 @@ static const struct file_operations aa_fs_profile_load = {
static ssize_t profile_replace(struct file *f, const char __user *buf, static ssize_t profile_replace(struct file *f, const char __user *buf,
size_t size, loff_t *pos) size_t size, loff_t *pos)
{ {
return policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY, struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
buf, size, pos); int error = policy_update(AA_MAY_LOAD_POLICY | AA_MAY_REPLACE_POLICY,
buf, size, pos, ns);
aa_put_ns(ns);
return error;
} }
static const struct file_operations aa_fs_profile_replace = { static const struct file_operations aa_fs_profile_replace = {
...@@ -166,17 +183,18 @@ static const struct file_operations aa_fs_profile_replace = { ...@@ -166,17 +183,18 @@ static const struct file_operations aa_fs_profile_replace = {
static ssize_t profile_remove(struct file *f, const char __user *buf, static ssize_t profile_remove(struct file *f, const char __user *buf,
size_t size, loff_t *pos) size_t size, loff_t *pos)
{ {
struct aa_loaddata *data;
struct aa_label *label; struct aa_label *label;
ssize_t error; ssize_t error;
char *data; struct aa_ns *ns = aa_get_ns(f->f_inode->i_private);
label = aa_begin_current_label(DO_UPDATE); label = aa_begin_current_label(DO_UPDATE);
/* high level check about policy management - fine grained in /* high level check about policy management - fine grained in
* below after unpack * below after unpack
*/ */
error = aa_may_manage_policy(label, AA_MAY_REMOVE_POLICY); error = aa_may_manage_policy(label, ns, AA_MAY_REMOVE_POLICY);
if (error) if (error)
return error; goto out;
/* /*
* aa_remove_profile needs a null terminated string so 1 extra * aa_remove_profile needs a null terminated string so 1 extra
...@@ -186,12 +204,14 @@ static ssize_t profile_remove(struct file *f, const char __user *buf, ...@@ -186,12 +204,14 @@ static ssize_t profile_remove(struct file *f, const char __user *buf,
error = PTR_ERR(data); error = PTR_ERR(data);
if (!IS_ERR(data)) { if (!IS_ERR(data)) {
data[size] = 0; data->data[size] = 0;
error = aa_remove_profiles(label, data, size); error = aa_remove_profiles(ns ? ns : labels_ns(label), label,
kvfree(data); data->data, size);
aa_put_loaddata(data);
} }
out:
aa_end_current_label(label); aa_end_current_label(label);
aa_put_ns(ns);
return error; return error;
} }
...@@ -234,6 +254,98 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, ...@@ -234,6 +254,98 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
aa_perms_accum_raw(perms, &tmp); aa_perms_accum_raw(perms, &tmp);
} }
/**
* 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 label_it i;
struct aa_label *label, *curr;
struct aa_profile *profile;
struct aa_data *data;
u32 bytes;
u32 blocks;
u32 size;
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 */
curr = aa_begin_current_label(DO_UPDATE);
label = aa_label_parse(curr, query, GFP_KERNEL, false, false);
aa_end_current_label(curr);
if (IS_ERR(label))
return PTR_ERR(label);
/* 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;
label_for_each_confined(i, label, profile) {
if (!profile->data)
continue;
data = rhashtable_lookup_fast(profile->data, &key,
profile->data->p);
if (data) {
if (out + sizeof(size) + data->size > buf + buf_len) {
aa_put_label(label);
return -EINVAL; /* not enough space */
}
size = __cpu_to_le32(data->size);
memcpy(out, &size, sizeof(size));
out += sizeof(size);
memcpy(out, data->data, data->size);
out += data->size;
blocks++;
}
}
aa_put_label(label);
bytes = out - buf - sizeof(bytes);
bytes = __cpu_to_le32(bytes);
blocks = __cpu_to_le32(blocks);
memcpy(buf, &bytes, sizeof(bytes));
memcpy(buf + sizeof(bytes), &blocks, sizeof(blocks));
return out - buf;
}
/** /**
* query_label - queries a label and writes permissions to buf * query_label - queries a label and writes permissions to buf
* @buf: the resulting permissions string is stored here (NOT NULL) * @buf: the resulting permissions string is stored here (NOT NULL)
...@@ -309,18 +421,27 @@ static ssize_t query_label(char *buf, size_t buf_len, ...@@ -309,18 +421,27 @@ static ssize_t query_label(char *buf, size_t buf_len,
#define QUERY_CMD_PROFILE_LEN 8 #define QUERY_CMD_PROFILE_LEN 8
#define QUERY_CMD_LABELALL "labelall\0" #define QUERY_CMD_LABELALL "labelall\0"
#define QUERY_CMD_LABELALL_LEN 9 #define QUERY_CMD_LABELALL_LEN 9
#define QUERY_CMD_DATA "data\0"
#define QUERY_CMD_DATA_LEN 5
/** /**
* aa_write_access - generic permissions query * aa_write_access - generic permissions and data query
* @file: pointer to open apparmorfs/access file * @file: pointer to open apparmorfs/access file
* @ubuf: user buffer containing the complete query string (NOT NULL) * @ubuf: user buffer containing the complete query string (NOT NULL)
* @count: size of ubuf * @count: size of ubuf
* @ppos: position in the file (MUST BE ZERO) * @ppos: position in the file (MUST BE ZERO)
* *
* Allows for one permission query per open(), write(), and read() sequence. * Allows for one permissions or data query per open(), write(), and read()
* The only query currently supported is a label-based query. For this query * sequence. The only queries currently supported are label-based queries for
* ubuf must begin with "label\0", followed by the profile query specific * permissions or data.
* format described in the query_label() function documentation. *
* 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 * Returns: number of bytes written or -errno on failure
*/ */
...@@ -352,6 +473,11 @@ static ssize_t aa_write_access(struct file *file, const char __user *ubuf, ...@@ -352,6 +473,11 @@ static ssize_t aa_write_access(struct file *file, const char __user *ubuf,
len = query_label(buf, SIMPLE_TRANSACTION_LIMIT, len = query_label(buf, SIMPLE_TRANSACTION_LIMIT,
buf + QUERY_CMD_LABELALL_LEN, buf + QUERY_CMD_LABELALL_LEN,
count - QUERY_CMD_LABELALL_LEN, false); count - QUERY_CMD_LABELALL_LEN, false);
} else 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 } else
len = -EINVAL; len = -EINVAL;
...@@ -537,6 +663,202 @@ static const struct file_operations aa_fs_seq_hash_fops = { ...@@ -537,6 +663,202 @@ static const struct file_operations aa_fs_seq_hash_fops = {
.release = single_release, .release = single_release,
}; };
static int aa_fs_seq_show_stacked(struct seq_file *seq, void *v)
{
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no");
aa_end_current_label(label);
return 0;
}
static int aa_fs_seq_open_stacked(struct inode *inode, struct file *file)
{
return single_open(file, aa_fs_seq_show_stacked, inode->i_private);
}
static const struct file_operations aa_fs_stacked = {
.owner = THIS_MODULE,
.open = aa_fs_seq_open_stacked,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int aa_fs_seq_show_ns_stacked(struct seq_file *seq, void *v)
{
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
struct aa_profile *profile;
struct label_it it;
int count = 1;
if (label->size > 1) {
label_for_each(it, label, profile)
if (profile->ns != labels_ns(label)) {
count++;
break;
}
}
seq_printf(seq, "%s\n", count > 1 ? "yes" : "no");
aa_end_current_label(label);
return 0;
}
static int aa_fs_seq_open_ns_stacked(struct inode *inode, struct file *file)
{
return single_open(file, aa_fs_seq_show_ns_stacked, inode->i_private);
}
static const struct file_operations aa_fs_ns_stacked = {
.owner = THIS_MODULE,
.open = aa_fs_seq_open_ns_stacked,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int aa_fs_seq_show_ns_level(struct seq_file *seq, void *v)
{
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
seq_printf(seq, "%d\n", labels_ns(label)->level);
aa_end_current_label(label);
return 0;
}
static int aa_fs_seq_open_ns_level(struct inode *inode, struct file *file)
{
return single_open(file, aa_fs_seq_show_ns_level, inode->i_private);
}
static const struct file_operations aa_fs_ns_level = {
.owner = THIS_MODULE,
.open = aa_fs_seq_open_ns_level,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int aa_fs_seq_show_ns_name(struct seq_file *seq, void *v)
{
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
seq_printf(seq, "%s\n", labels_ns(label)->base.name);
aa_end_current_label(label);
return 0;
}
static int aa_fs_seq_open_ns_name(struct inode *inode, struct file *file)
{
return single_open(file, aa_fs_seq_show_ns_name, inode->i_private);
}
static const struct file_operations aa_fs_ns_name = {
.owner = THIS_MODULE,
.open = aa_fs_seq_open_ns_name,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int rawdata_release(struct inode *inode, struct file *file)
{
/* TODO: switch to loaddata when profile switched to symlink */
aa_put_proxy(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_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
if (profile->rawdata->abi) {
seq_printf(seq, "v%d", profile->rawdata->abi);
seq_puts(seq, "\n");
}
aa_put_label(label);
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_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
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_label(label);
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_proxy *proxy = file->private_data;
struct aa_label *label = aa_get_label_rcu(&proxy->label);
struct aa_profile *profile = labels_profile(label);
ssize_t ret = simple_read_from_buffer(buf, size, ppos, profile->rawdata->data, profile->rawdata->size);
aa_put_label(label);
return ret;
}
static int rawdata_open(struct inode *inode, struct file *file)
{
if (!policy_view_capable(NULL))
return -EACCES;
file->private_data = aa_get_proxy(inode->i_private);
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 **/ /** fns to setup dynamic per profile/namespace files **/
/** /**
...@@ -668,6 +990,29 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) ...@@ -668,6 +990,29 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
profile->dents[AAFS_PROF_HASH] = dent; profile->dents[AAFS_PROF_HASH] = dent;
} }
if (profile->rawdata) {
dent = create_profile_file(dir, "raw_hash", 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->label.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->label.proxy);
}
list_for_each_entry(child, &profile->base.profiles, base.list) { list_for_each_entry(child, &profile->base.profiles, base.list) {
error = __aa_fs_profile_mkdir(child, prof_child_dir(profile)); error = __aa_fs_profile_mkdir(child, prof_child_dir(profile));
if (error) if (error)
...@@ -685,6 +1030,88 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) ...@@ -685,6 +1030,88 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
return error; return error;
} }
static int ns_mkdir_op(struct inode *dir, struct dentry *dentry, umode_t mode)
{
struct aa_ns *ns, *parent;
/* TODO: improve permission check */
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
int error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY);
aa_end_current_label(label);
if (error)
return error;
parent = aa_get_ns(dir->i_private);
AA_BUG(d_inode(ns_subns_dir(parent)) != dir);
/* we have to unlock and then relock to get locking order right
* for pin_fs
*/
inode_unlock(dir);
securityfs_pin_fs();
inode_lock_nested(dir, I_MUTEX_PARENT);
error = __securityfs_setup_d_inode(dir, dentry, mode | S_IFDIR, NULL,
NULL, NULL);
if (error)
return error;
ns = aa_create_ns(parent, ACCESS_ONCE(dentry->d_name.name), dentry);
if (IS_ERR(ns)) {
error = PTR_ERR(ns);
ns = NULL;
}
aa_put_ns(ns); /* list ref remains */
aa_put_ns(parent);
return error;
}
static int ns_rmdir_op(struct inode *dir, struct dentry *dentry)
{
struct aa_ns *ns, *parent;
/* TODO: improve permission check */
struct aa_label *label = aa_begin_current_label(DO_UPDATE);
int error = aa_may_manage_policy(label, NULL, AA_MAY_LOAD_POLICY);
aa_end_current_label(label);
if (error)
return error;
parent = aa_get_ns(dir->i_private);
/* rmdir calls the generic securityfs functions to remove files
* from the apparmor dir. It is up to the apparmor ns locking
* to avoid races.
*/
inode_unlock(dir);
inode_unlock(dentry->d_inode);
mutex_lock(&parent->lock);
ns = aa_get_ns(__aa_findn_ns(&parent->sub_ns, dentry->d_name.name,
dentry->d_name.len));
if (!ns) {
error = -ENOENT;
goto out;
}
AA_BUG(ns_dir(ns) != dentry);
__aa_remove_ns(ns);
aa_put_ns(ns);
out:
mutex_unlock(&parent->lock);
inode_lock_nested(dir, I_MUTEX_PARENT);
inode_lock(dentry->d_inode);
aa_put_ns(parent);
return error;
}
static const struct inode_operations ns_dir_inode_operations = {
.lookup = simple_lookup,
.mkdir = ns_mkdir_op,
.rmdir = ns_rmdir_op,
};
/** /**
* *
* Requires: @ns->lock held * Requires: @ns->lock held
...@@ -708,21 +1135,91 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns) ...@@ -708,21 +1135,91 @@ void __aa_fs_ns_rmdir(struct aa_ns *ns)
mutex_unlock(&sub->lock); mutex_unlock(&sub->lock);
} }
if (ns_subns_dir(ns)) {
sub = d_inode(ns_subns_dir(ns))->i_private;
aa_put_ns(sub);
}
if (ns_subload(ns)) {
sub = d_inode(ns_subload(ns))->i_private;
aa_put_ns(sub);
}
if (ns_subreplace(ns)) {
sub = d_inode(ns_subreplace(ns))->i_private;
aa_put_ns(sub);
}
if (ns_subremove(ns)) {
sub = d_inode(ns_subremove(ns))->i_private;
aa_put_ns(sub);
}
for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) { for (i = AAFS_NS_SIZEOF - 1; i >= 0; --i) {
securityfs_remove(ns->dents[i]); securityfs_remove(ns->dents[i]);
ns->dents[i] = NULL; ns->dents[i] = NULL;
} }
} }
/* assumes cleanup in caller */
static int __aa_fs_ns_mkdir_entries(struct aa_ns *ns, struct dentry *dir)
{
struct dentry *dent;
AA_BUG(!ns);
AA_BUG(!dir);
dent = securityfs_create_dir("profiles", dir);
if (IS_ERR(dent))
return PTR_ERR(dent);
ns_subprofs_dir(ns) = dent;
dent = securityfs_create_dir("raw_data", dir);
if (IS_ERR(dent))
return PTR_ERR(dent);
ns_subdata_dir(ns) = dent;
dent = securityfs_create_file(".load", 0666, dir, ns,
&aa_fs_profile_load);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subload(ns) = dent;
dent = securityfs_create_file(".replace", 0666, dir, ns,
&aa_fs_profile_replace);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subreplace(ns) = dent;
dent = securityfs_create_file(".remove", 0666, dir, ns,
&aa_fs_profile_remove);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subremove(ns) = dent;
/* use create_dentry so we can supply private data */
dent = securityfs_create_dentry("namespaces",
S_IFDIR | S_IRWXU | S_IRUGO | S_IXUGO,
dir, ns, NULL,
&ns_dir_inode_operations);
if (IS_ERR(dent))
return PTR_ERR(dent);
aa_get_ns(ns);
ns_subns_dir(ns) = dent;
return 0;
}
/** /**
* *
* Requires: @ns->lock held * Requires: @ns->lock held
*/ */
int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
struct dentry *dent)
{ {
struct aa_ns *sub; struct aa_ns *sub;
struct aa_profile *child; struct aa_profile *child;
struct dentry *dent, *dir; struct dentry *dir;
int error; int error;
AA_BUG(!ns); AA_BUG(!ns);
...@@ -732,30 +1229,29 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) ...@@ -732,30 +1229,29 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
if (!name) if (!name)
name = ns->base.name; name = ns->base.name;
if (!dent) {
/* create ns dir if it doesn't already exist */
dent = securityfs_create_dir(name, parent); dent = securityfs_create_dir(name, parent);
if (IS_ERR(dent)) if (IS_ERR(dent))
goto fail; goto fail;
} else
dget(dent);
ns_dir(ns) = dir = dent; ns_dir(ns) = dir = dent;
error = __aa_fs_ns_mkdir_entries(ns, dir);
if (error)
goto fail2;
dent = securityfs_create_dir("profiles", dir); /* profiles */
if (IS_ERR(dent))
goto fail;
ns_subprofs_dir(ns) = dent;
dent = securityfs_create_dir("namespaces", dir);
if (IS_ERR(dent))
goto fail;
ns_subns_dir(ns) = dent;
list_for_each_entry(child, &ns->base.profiles, base.list) { list_for_each_entry(child, &ns->base.profiles, base.list) {
error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns)); error = __aa_fs_profile_mkdir(child, ns_subprofs_dir(ns));
if (error) if (error)
goto fail2; goto fail2;
} }
/* subnamespaces */
list_for_each_entry(sub, &ns->sub_ns, base.list) { list_for_each_entry(sub, &ns->sub_ns, base.list) {
mutex_lock(&sub->lock); mutex_lock(&sub->lock);
error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL); error = __aa_fs_ns_mkdir(sub, ns_subns_dir(ns), NULL, NULL);
mutex_unlock(&sub->lock); mutex_unlock(&sub->lock);
if (error) if (error)
goto fail2; goto fail2;
...@@ -773,8 +1269,6 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name) ...@@ -773,8 +1269,6 @@ int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name)
} }
#define list_entry_next(pos, member) \
list_entry(pos->member.next, typeof(*pos), member)
#define list_entry_is_head(pos, head, member) (&pos->member == (head)) #define list_entry_is_head(pos, head, member) (&pos->member == (head))
/** /**
...@@ -808,7 +1302,7 @@ static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns) ...@@ -808,7 +1302,7 @@ static struct aa_ns *__next_ns(struct aa_ns *root, struct aa_ns *ns)
parent = ns->parent; parent = ns->parent;
while (ns != root) { while (ns != root) {
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
next = list_entry_next(ns, base.list); next = list_next_entry(ns, base.list);
if (!list_entry_is_head(next, &parent->sub_ns, base.list)) { if (!list_entry_is_head(next, &parent->sub_ns, base.list)) {
mutex_lock(&next->lock); mutex_lock(&next->lock);
return next; return next;
...@@ -866,7 +1360,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p) ...@@ -866,7 +1360,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p)
parent = rcu_dereference_protected(p->parent, parent = rcu_dereference_protected(p->parent,
mutex_is_locked(&p->ns->lock)); mutex_is_locked(&p->ns->lock));
while (parent) { while (parent) {
p = list_entry_next(p, base.list); p = list_next_entry(p, base.list);
if (!list_entry_is_head(p, &parent->base.profiles, base.list)) if (!list_entry_is_head(p, &parent->base.profiles, base.list))
return p; return p;
p = parent; p = parent;
...@@ -875,7 +1369,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p) ...@@ -875,7 +1369,7 @@ static struct aa_profile *__next_profile(struct aa_profile *p)
} }
/* is next another profile in the namespace */ /* is next another profile in the namespace */
p = list_entry_next(p, base.list); p = list_next_entry(p, base.list);
if (!list_entry_is_head(p, &ns->base.profiles, base.list)) if (!list_entry_is_head(p, &ns->base.profiles, base.list))
return p; return p;
...@@ -994,7 +1488,7 @@ static const struct seq_operations aa_fs_profiles_op = { ...@@ -994,7 +1488,7 @@ static const struct seq_operations aa_fs_profiles_op = {
static int profiles_open(struct inode *inode, struct file *file) static int profiles_open(struct inode *inode, struct file *file)
{ {
if (!aa_may_open_profiles()) if (!policy_view_capable(NULL))
return -EACCES; return -EACCES;
return seq_open(file, &aa_fs_profiles_op); return seq_open(file, &aa_fs_profiles_op);
...@@ -1036,6 +1530,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = { ...@@ -1036,6 +1530,7 @@ static struct aa_fs_entry aa_fs_entry_domain[] = {
AA_FS_FILE_BOOLEAN("change_onexec", 1), AA_FS_FILE_BOOLEAN("change_onexec", 1),
AA_FS_FILE_BOOLEAN("change_profile", 1), AA_FS_FILE_BOOLEAN("change_profile", 1),
AA_FS_FILE_BOOLEAN("stack", 1), AA_FS_FILE_BOOLEAN("stack", 1),
AA_FS_FILE_STRING("version", "1.2"),
{ } { }
}; };
...@@ -1085,10 +1580,11 @@ static struct aa_fs_entry aa_fs_entry_features[] = { ...@@ -1085,10 +1580,11 @@ 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(".load", 0666, &aa_fs_profile_load),
AA_FS_FILE_FOPS(".replace", 0666, &aa_fs_profile_replace),
AA_FS_FILE_FOPS(".remove", 0666, &aa_fs_profile_remove),
AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access), AA_FS_FILE_FOPS(".access", 0666, &aa_fs_access),
AA_FS_FILE_FOPS(".stacked", 0666, &aa_fs_stacked),
AA_FS_FILE_FOPS(".ns_stacked", 0666, &aa_fs_ns_stacked),
AA_FS_FILE_FOPS(".ns_level", 0666, &aa_fs_ns_level),
AA_FS_FILE_FOPS(".ns_name", 0666, &aa_fs_ns_name),
AA_FS_FILE_FOPS("profiles", 0444, &aa_fs_profiles_fops), AA_FS_FILE_FOPS("profiles", 0444, &aa_fs_profiles_fops),
AA_FS_DIR("features", aa_fs_entry_features), AA_FS_DIR("features", aa_fs_entry_features),
{ } { }
...@@ -1212,7 +1708,7 @@ static int aa_mk_null_file(struct dentry *parent) ...@@ -1212,7 +1708,7 @@ static int aa_mk_null_file(struct dentry *parent)
if (error) if (error)
return error; return error;
mutex_lock(&parent->d_inode->i_mutex); inode_lock(d_inode(parent));
dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME)); dentry = lookup_one_len(NULL_FILE_NAME, parent, strlen(NULL_FILE_NAME));
if (IS_ERR(dentry)) { if (IS_ERR(dentry)) {
error = PTR_ERR(dentry); error = PTR_ERR(dentry);
...@@ -1238,7 +1734,7 @@ static int aa_mk_null_file(struct dentry *parent) ...@@ -1238,7 +1734,7 @@ static int aa_mk_null_file(struct dentry *parent)
out1: out1:
dput(dentry); dput(dentry);
out: out:
mutex_unlock(&parent->d_inode->i_mutex); inode_unlock(d_inode(parent));
simple_release_fs(&mount, &count); simple_release_fs(&mount, &count);
return error; return error;
} }
...@@ -1252,6 +1748,7 @@ static int aa_mk_null_file(struct dentry *parent) ...@@ -1252,6 +1748,7 @@ static int aa_mk_null_file(struct dentry *parent)
*/ */
static int __init aa_create_aafs(void) static int __init aa_create_aafs(void)
{ {
struct dentry *dent;
int error; int error;
if (!apparmor_initialized) if (!apparmor_initialized)
...@@ -1267,8 +1764,32 @@ static int __init aa_create_aafs(void) ...@@ -1267,8 +1764,32 @@ static int __init aa_create_aafs(void)
if (error) if (error)
goto error; goto error;
dent = securityfs_create_file(".load", 0666, aa_fs_entry.dentry,
NULL, &aa_fs_profile_load);
if (IS_ERR(dent)) {
error = PTR_ERR(dent);
goto error;
}
ns_subload(root_ns) = dent;
dent = securityfs_create_file(".replace", 0666, aa_fs_entry.dentry,
NULL, &aa_fs_profile_replace);
if (IS_ERR(dent)) {
error = PTR_ERR(dent);
goto error;
}
ns_subreplace(root_ns) = dent;
dent = securityfs_create_file(".remove", 0666, aa_fs_entry.dentry,
NULL, &aa_fs_profile_remove);
if (IS_ERR(dent)) {
error = PTR_ERR(dent);
goto error;
}
ns_subremove(root_ns) = dent;
mutex_lock(&root_ns->lock); mutex_lock(&root_ns->lock);
error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy"); error = __aa_fs_ns_mkdir(root_ns, aa_fs_entry.dentry, "policy", NULL);
mutex_unlock(&root_ns->lock); mutex_unlock(&root_ns->lock);
if (error) if (error)
......
/*
* AppArmor security module
*
* This file contains AppArmor file mediation function definitions.
*
* Copyright 2014 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
*
* This is a file of helper fns backported from newer kernels to support
* backporting of apparmor to older kernels. Fns prefixed with code they
* are copied of modified from
*/
#include "include/backport.h"
...@@ -29,6 +29,43 @@ unsigned int aa_hash_size(void) ...@@ -29,6 +29,43 @@ unsigned int aa_hash_size(void)
return apparmor_hash_size; 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, int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len) size_t len)
{ {
...@@ -39,6 +76,9 @@ int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, ...@@ -39,6 +76,9 @@ int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
int error = -ENOMEM; int error = -ENOMEM;
u32 le32_version = cpu_to_le32(version); u32 le32_version = cpu_to_le32(version);
if (!aa_g_hash_policy)
return 0;
if (!apparmor_tfm) if (!apparmor_tfm)
return 0; return 0;
......
...@@ -488,27 +488,41 @@ static struct aa_label *x_to_label(struct aa_profile *profile, ...@@ -488,27 +488,41 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
} }
static struct aa_label *profile_transition(struct aa_profile *profile, static struct aa_label *profile_transition(struct aa_profile *profile,
const char *name, const struct linux_binprm *bprm,
struct path_cond *cond, char *buffer, struct path_cond *cond,
bool *secure_exec) bool *secure_exec)
{ {
struct aa_label *new = NULL; struct aa_label *new = NULL;
const char *info = NULL; const char *info = NULL, *name = NULL, *target = NULL;
unsigned int state = profile->file.start; unsigned int state = profile->file.start;
struct aa_perms perms = {}; struct aa_perms perms = {};
const char *target = NULL;
int error = 0; int error = 0;
AA_BUG(!profile);
AA_BUG(!bprm);
AA_BUG(!buffer);
error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
&name, &info, profile->disconnected);
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
AA_DEBUG("name lookup ix on error");
error = 0;
new = aa_get_newest_label(&profile->label);
}
name = bprm->filename;
goto audit;
}
if (profile_unconfined(profile)) { if (profile_unconfined(profile)) {
new = find_attach(profile->ns, &profile->ns->base.profiles, new = find_attach(profile->ns, &profile->ns->base.profiles,
name); name);
if (new) { if (new) {
AA_DEBUG("unconfined attached to new label"); AA_DEBUG("unconfined attached to new label");
return new; return new;
} }
AA_DEBUG("unconfined exec no attachment"); AA_DEBUG("unconfined exec no attachment");
return aa_get_newest_label(&profile->label); return aa_get_newest_label(&profile->label);
} }
...@@ -565,14 +579,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile, ...@@ -565,14 +579,20 @@ static struct aa_label *profile_transition(struct aa_profile *profile,
} }
static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
bool stack, const char *xname, struct path_cond *cond, bool stack, const struct linux_binprm *bprm,
char *buffer, struct path_cond *cond,
bool *secure_exec) bool *secure_exec)
{ {
unsigned int state = profile->file.start; unsigned int state = profile->file.start;
struct aa_perms perms = {}; struct aa_perms perms = {};
const char *info = "change_profile onexec"; const char *xname = NULL, *info = "change_profile onexec";
int error = -EACCES; int error = -EACCES;
AA_BUG(!profile);
AA_BUG(!onexec);
AA_BUG(!bprm);
AA_BUG(!buffer);
if (profile_unconfined(profile)) { if (profile_unconfined(profile)) {
/* change_profile on exec already granted */ /* change_profile on exec already granted */
/* /*
...@@ -583,6 +603,18 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, ...@@ -583,6 +603,18 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
return 0; return 0;
} }
error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
&xname, &info, profile->disconnected);
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR)) {
AA_DEBUG("name lookup ix on error");
error = 0;
}
xname = bprm->filename;
goto audit;
}
/* find exec permissions for name */ /* find exec permissions for name */
state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms); state = aa_str_perms(profile->file.dfa, state, xname, cond, &perms);
if (!(perms.allow & AA_MAY_ONEXEC)) { if (!(perms.allow & AA_MAY_ONEXEC)) {
...@@ -618,46 +650,52 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec, ...@@ -618,46 +650,52 @@ static int profile_onexec(struct aa_profile *profile, struct aa_label *onexec,
static struct aa_label *handle_onexec(struct aa_label *label, static struct aa_label *handle_onexec(struct aa_label *label,
struct aa_label *onexec, bool stack, struct aa_label *onexec, bool stack,
const char *xname, const struct linux_binprm *bprm,
struct path_cond *cond, char *buffer, struct path_cond *cond,
bool *unsafe) bool *unsafe)
{ {
struct aa_profile *profile; struct aa_profile *profile;
struct aa_label *new; struct aa_label *new;
int error; int error;
AA_BUG(!label);
AA_BUG(!onexec);
AA_BUG(!bprm);
AA_BUG(!buffer);
if (!stack) { if (!stack) {
error = fn_for_each_in_ns(label, profile, error = fn_for_each_in_ns(label, profile,
profile_onexec(profile, onexec, stack, profile_onexec(profile, onexec, stack,
xname, cond, unsafe)); bprm, buffer, cond, unsafe));
if (error) if (error)
return ERR_PTR(error); return ERR_PTR(error);
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, new = fn_label_build_in_ns(label, profile, GFP_ATOMIC,
aa_get_newest_label(onexec), aa_get_newest_label(onexec),
profile_transition(profile, xname, profile_transition(profile, bprm, buffer,
cond, unsafe)); cond, unsafe));
} else { } else {
/* TODO: determine how much we want to losen this */ /* TODO: determine how much we want to losen this */
error = fn_for_each_in_ns(label, profile, error = fn_for_each_in_ns(label, profile,
profile_onexec(profile, onexec, stack, xname, profile_onexec(profile, onexec, stack, bprm,
cond, unsafe)); buffer, cond, unsafe));
if (error) if (error)
return ERR_PTR(error); return ERR_PTR(error);
new = fn_label_build_in_ns(label, profile, GFP_ATOMIC, new = fn_label_build_in_ns(label, profile, GFP_ATOMIC,
aa_label_merge(&profile->label, aa_label_merge(&profile->label, onexec,
onexec,
GFP_ATOMIC), GFP_ATOMIC),
profile_transition(profile, xname, profile_transition(profile, bprm, buffer,
cond, unsafe)); cond, unsafe));
} }
if (new) if (new)
return new; return new;
/* TODO: get rid of GLOBAL_ROOT_UID */
error = fn_for_each_in_ns(label, profile, error = fn_for_each_in_ns(label, profile,
aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC, aa_audit_file(profile, &nullperms, OP_CHANGE_ONEXEC,
AA_MAY_ONEXEC, xname, NULL, onexec, AA_MAY_ONEXEC, bprm->filename, NULL,
GLOBAL_ROOT_UID, onexec, GLOBAL_ROOT_UID,
"failed to build target label", -ENOMEM)); "failed to build target label", -ENOMEM));
return ERR_PTR(error); return ERR_PTR(error);
} }
...@@ -676,7 +714,6 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -676,7 +714,6 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
struct aa_label *label, *new = NULL; struct aa_label *label, *new = NULL;
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL; char *buffer = NULL;
const char *xname = NULL;
const char *info = NULL; const char *info = NULL;
int error = 0; int error = 0;
bool unsafe = false; bool unsafe = false;
...@@ -692,28 +729,17 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -692,28 +729,17 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
AA_BUG(!ctx); AA_BUG(!ctx);
label = aa_get_newest_label(ctx->label); label = aa_get_newest_label(ctx->label);
profile = labels_profile(label);
/* buffer freed below, xname is pointer into buffer */ /* buffer freed below, name is pointer into buffer */
get_buffers(buffer); get_buffers(buffer);
error = aa_path_name(&bprm->file->f_path, profile->path_flags, buffer,
&xname, &info, profile->disconnected);
if (error) {
if (profile_unconfined(profile) ||
(profile->label.flags & FLAG_IX_ON_NAME_ERROR))
error = 0;
xname = bprm->filename;
goto audit;
}
/* Test for onexec first as onexec override other x transitions. */ /* Test for onexec first as onexec override other x transitions. */
if (ctx->onexec) if (ctx->onexec)
new = handle_onexec(label, ctx->onexec, ctx->token, xname, new = handle_onexec(label, ctx->onexec, ctx->token,
&cond, &unsafe); bprm, buffer, &cond, &unsafe);
else else
new = fn_label_build(label, profile, GFP_ATOMIC, new = fn_label_build(label, profile, GFP_ATOMIC,
profile_transition(profile, xname, &cond, profile_transition(profile, bprm, buffer,
&unsafe)); &cond, &unsafe));
AA_BUG(!new); AA_BUG(!new);
if (IS_ERR(new)) { if (IS_ERR(new)) {
...@@ -731,7 +757,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -731,7 +757,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
* subsets are allowed even when no_new_privs is set because this * subsets are allowed even when no_new_privs is set because this
* aways results in a further reduction of permissions. * aways results in a further reduction of permissions.
*/ */
if (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS && if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) &&
!unconfined(label) && !aa_label_is_subset(new, label)) { !unconfined(label) && !aa_label_is_subset(new, label)) {
error = -EPERM; error = -EPERM;
info = "no new privs"; info = "no new privs";
...@@ -753,7 +779,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -753,7 +779,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
if (unsafe) { if (unsafe) {
if (DEBUG_ON) { if (DEBUG_ON) {
dbg_printk("scrubbing environment variables for %s " dbg_printk("scrubbing environment variables for %s "
"label=", xname); "label=", bprm->filename);
aa_label_printk(new, GFP_ATOMIC); aa_label_printk(new, GFP_ATOMIC);
dbg_printk("\n"); dbg_printk("\n");
} }
...@@ -764,7 +790,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -764,7 +790,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
/* when transitioning clear unsafe personality bits */ /* when transitioning clear unsafe personality bits */
if (DEBUG_ON) { if (DEBUG_ON) {
dbg_printk("apparmor: clearing unsafe personality " dbg_printk("apparmor: clearing unsafe personality "
"bits. %s label=", xname); "bits. %s label=", bprm->filename);
aa_label_printk(new, GFP_ATOMIC); aa_label_printk(new, GFP_ATOMIC);
dbg_printk("\n"); dbg_printk("\n");
} }
...@@ -786,7 +812,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) ...@@ -786,7 +812,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm)
audit: audit:
error = fn_for_each(label, profile, error = fn_for_each(label, profile,
aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC,
xname, NULL, new, bprm->filename, NULL, new,
file_inode(bprm->file)->i_uid, info, file_inode(bprm->file)->i_uid, info,
error)); error));
aa_put_label(new); aa_put_label(new);
......
...@@ -107,7 +107,8 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms, ...@@ -107,7 +107,8 @@ int aa_audit_file(struct aa_profile *profile, struct aa_perms *perms,
{ {
int type = AUDIT_APPARMOR_AUTO; int type = AUDIT_APPARMOR_AUTO;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, op); DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_TASK, op);
sa.u.tsk = NULL;
aad(&sa)->request = request; aad(&sa)->request = request;
aad(&sa)->name = name; aad(&sa)->name = name;
aad(&sa)->fs.target = target; aad(&sa)->fs.target = target;
...@@ -164,18 +165,15 @@ static inline bool is_deleted(struct dentry *dentry) ...@@ -164,18 +165,15 @@ static inline bool is_deleted(struct dentry *dentry)
return 0; return 0;
} }
static int path_name(const char *op, struct aa_label *label, struct path *path, static int path_name(const char *op, struct aa_label *label,
int flags, char *buffer, const char**name, const struct path *path, int flags, char *buffer,
struct path_cond *cond, u32 request, bool delegate_deleted) const char**name, struct path_cond *cond, u32 request)
{ {
struct aa_profile *profile; struct aa_profile *profile;
const char *info = NULL; const char *info = NULL;
int error = aa_path_name(path, flags, buffer, name, &info, int error = aa_path_name(path, flags, buffer, name, &info,
labels_profile(label)->disconnected); labels_profile(label)->disconnected);
if (error) { if (error) {
if (error == -ENOENT && is_deleted(path->dentry) &&
delegate_deleted)
return 0;
fn_for_each_confined(label, profile, fn_for_each_confined(label, profile,
aa_audit_file(profile, &nullperms, op, request, *name, aa_audit_file(profile, &nullperms, op, request, *name,
NULL, NULL, cond->uid, info, error)); NULL, NULL, cond->uid, info, error));
...@@ -288,6 +286,7 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, ...@@ -288,6 +286,7 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
struct aa_perms *perms) struct aa_perms *perms)
{ {
int e = 0; int e = 0;
if (profile_unconfined(profile) || if (profile_unconfined(profile) ||
((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX))) ((flags & PATH_SOCK_COND) && !PROFILE_MEDIATES_AF(profile, AF_UNIX)))
return 0; return 0;
...@@ -298,6 +297,27 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, ...@@ -298,6 +297,27 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
cond->uid, NULL, e); cond->uid, NULL, e);
} }
static int profile_path_perm(const char *op, struct aa_profile *profile,
const struct path *path, char *buffer, u32 request,
struct path_cond *cond, int flags,
struct aa_perms *perms)
{
const char *name;
int error;
if (profile_unconfined(profile))
return 0;
error = path_name(op, &profile->label, path,
flags | profile->path_flags, buffer, &name, cond,
request);
if (error)
return error;
return __aa_path_perm(op, profile, name, request, cond, flags,
perms);
}
/** /**
* aa_path_perm - do permissions check & audit for @path * aa_path_perm - do permissions check & audit for @path
* @op: operation being checked * @op: operation being checked
...@@ -309,26 +329,20 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, ...@@ -309,26 +329,20 @@ int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name,
* *
* Returns: %0 else error if access denied or other error * Returns: %0 else error if access denied or other error
*/ */
int aa_path_perm(const char *op, struct aa_label *label, struct path *path, int aa_path_perm(const char *op, struct aa_label *label,
int flags, u32 request, struct path_cond *cond) const struct path *path, int flags, u32 request,
struct path_cond *cond)
{ {
struct aa_perms perms = {}; struct aa_perms perms = {};
char *buffer = NULL;
const char *name;
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL;
int error; int error;
/* TODO: fix path lookup flags */ flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
flags |= labels_profile(label)->path_flags |
(S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
get_buffers(buffer); get_buffers(buffer);
error = path_name(op, label, path, flags, buffer, &name, cond,
request, true);
if (!error)
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
__aa_path_perm(op, profile, name, request, cond, profile_path_perm(op, profile, path, buffer, request,
flags, &perms)); cond, flags, &perms));
put_buffers(buffer); put_buffers(buffer);
return error; return error;
...@@ -354,15 +368,30 @@ static inline bool xindex_is_subset(u32 link, u32 target) ...@@ -354,15 +368,30 @@ static inline bool xindex_is_subset(u32 link, u32 target)
return 1; return 1;
} }
static int profile_path_link(struct aa_profile *profile, const char *lname, static int profile_path_link(struct aa_profile *profile,
const char *tname, struct path_cond *cond) const struct path *link, char *buffer,
const struct path *target, char *buffer2,
struct path_cond *cond)
{ {
const char *lname, *tname = NULL;
struct aa_perms lperms, perms; struct aa_perms lperms, perms;
const char *info = NULL; const char *info = NULL;
u32 request = AA_MAY_LINK; u32 request = AA_MAY_LINK;
unsigned int state; unsigned int state;
int e = -EACCES; int error;
error = path_name(OP_LINK, &profile->label, link, profile->path_flags,
buffer, &lname, cond, AA_MAY_LINK);
if (error)
goto audit;
/* buffer2 freed below, tname is pointer in buffer2 */
error = path_name(OP_LINK, &profile->label, target, profile->path_flags,
buffer2, &tname, cond, AA_MAY_LINK);
if (error)
goto audit;
error = -EACCES;
/* aa_str_perms - handles the case of the dfa being NULL */ /* aa_str_perms - handles the case of the dfa being NULL */
state = aa_str_perms(profile->file.dfa, profile->file.start, lname, state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
cond, &lperms); cond, &lperms);
...@@ -413,11 +442,11 @@ static int profile_path_link(struct aa_profile *profile, const char *lname, ...@@ -413,11 +442,11 @@ static int profile_path_link(struct aa_profile *profile, const char *lname,
} }
done_tests: done_tests:
e = 0; error = 0;
audit: audit:
return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname, return aa_audit_file(profile, &lperms, OP_LINK, request, lname, tname,
NULL, cond->uid, info, e); NULL, cond->uid, info, error);
} }
/** /**
...@@ -439,7 +468,7 @@ static int profile_path_link(struct aa_profile *profile, const char *lname, ...@@ -439,7 +468,7 @@ static int profile_path_link(struct aa_profile *profile, const char *lname,
* Returns: %0 if allowed else error * Returns: %0 if allowed else error
*/ */
int aa_path_link(struct aa_label *label, struct dentry *old_dentry, int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry) const struct path *new_dir, struct dentry *new_dentry)
{ {
struct path link = { new_dir->mnt, new_dentry }; struct path link = { new_dir->mnt, new_dentry };
struct path target = { new_dir->mnt, old_dentry }; struct path target = { new_dir->mnt, old_dentry };
...@@ -448,31 +477,14 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry, ...@@ -448,31 +477,14 @@ int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
d_backing_inode(old_dentry)->i_mode d_backing_inode(old_dentry)->i_mode
}; };
char *buffer = NULL, *buffer2 = NULL; char *buffer = NULL, *buffer2 = NULL;
const char *lname, *tname = NULL;
struct aa_profile *profile; struct aa_profile *profile;
int error; int error;
/* TODO: fix path lookup flags, auditing of failed path for profile */
profile = labels_profile(label);
/* buffer freed below, lname is pointer in buffer */ /* buffer freed below, lname is pointer in buffer */
get_buffers(buffer, buffer2); get_buffers(buffer, buffer2);
error = path_name(OP_LINK, label, &link,
labels_profile(label)->path_flags, buffer,
&lname, &cond, AA_MAY_LINK, false);
if (error)
goto out;
/* buffer2 freed below, tname is pointer in buffer2 */
error = path_name(OP_LINK, label, &target,
labels_profile(label)->path_flags, buffer2, &tname,
&cond, AA_MAY_LINK, false);
if (error)
goto out;
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
profile_path_link(profile, lname, tname, &cond)); profile_path_link(profile, &link, buffer, &target,
buffer2, &cond));
out:
put_buffers(buffer, buffer2); put_buffers(buffer, buffer2);
return error; return error;
...@@ -509,7 +521,6 @@ static int __file_path_perm(const char *op, struct aa_label *label, ...@@ -509,7 +521,6 @@ static int __file_path_perm(const char *op, struct aa_label *label,
.uid = file_inode(file)->i_uid, .uid = file_inode(file)->i_uid,
.mode = file_inode(file)->i_mode .mode = file_inode(file)->i_mode
}; };
const char *name;
char *buffer; char *buffer;
int flags, error; int flags, error;
...@@ -518,27 +529,13 @@ static int __file_path_perm(const char *op, struct aa_label *label, ...@@ -518,27 +529,13 @@ static int __file_path_perm(const char *op, struct aa_label *label,
/* TODO: check for revocation on stale profiles */ /* TODO: check for revocation on stale profiles */
return 0; return 0;
/* TODO: fix path lookup flags */ flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
flags = PATH_DELEGATE_DELETED | labels_profile(label)->path_flags |
(S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
get_buffers(buffer); get_buffers(buffer);
error = path_name(op, label, &file->f_path, flags, buffer, &name, &cond,
request, true);
if (error) {
if (error == 1)
/* Access to open files that are deleted are
* given a pass (implicit delegation)
*/
/* TODO not needed when full perms cached */
error = 0;
goto out;
}
/* check every profile in task label not in current cache */ /* check every profile in task label not in current cache */
error = fn_for_each_not_in_set(flabel, label, profile, error = fn_for_each_not_in_set(flabel, label, profile,
__aa_path_perm(op, profile, name, request, &cond, 0, profile_path_perm(op, profile, &file->f_path, buffer,
&perms)); request, &cond, flags, &perms));
if (denied) { if (denied) {
/* check every profile in file label that was not tested /* check every profile in file label that was not tested
* in the initial check above. * in the initial check above.
...@@ -548,13 +545,13 @@ static int __file_path_perm(const char *op, struct aa_label *label, ...@@ -548,13 +545,13 @@ static int __file_path_perm(const char *op, struct aa_label *label,
/* TODO: don't audit here */ /* TODO: don't audit here */
last_error(error, last_error(error,
fn_for_each_not_in_set(label, flabel, profile, fn_for_each_not_in_set(label, flabel, profile,
__aa_path_perm(op, profile, name, request, profile_path_perm(op, profile, &file->f_path,
&cond, 0, &perms))); buffer, request, &cond, flags,
&perms)));
} }
if (!error) if (!error)
update_file_ctx(file_ctx(file), label, request); update_file_ctx(file_ctx(file), label, request);
out:
put_buffers(buffer); put_buffers(buffer);
return error; return error;
......
...@@ -17,6 +17,8 @@ ...@@ -17,6 +17,8 @@
#include <linux/types.h> #include <linux/types.h>
#include "backport.h"
/* /*
* Class of mediation types in the AppArmor policy db * Class of mediation types in the AppArmor policy db
*/ */
......
...@@ -70,6 +70,10 @@ enum aafs_ns_type { ...@@ -70,6 +70,10 @@ enum aafs_ns_type {
AAFS_NS_DIR, AAFS_NS_DIR,
AAFS_NS_PROFS, AAFS_NS_PROFS,
AAFS_NS_NS, AAFS_NS_NS,
AAFS_NS_RAW_DATA,
AAFS_NS_LOAD,
AAFS_NS_REPLACE,
AAFS_NS_REMOVE,
AAFS_NS_COUNT, AAFS_NS_COUNT,
AAFS_NS_MAX_COUNT, AAFS_NS_MAX_COUNT,
AAFS_NS_SIZE, AAFS_NS_SIZE,
...@@ -85,12 +89,19 @@ enum aafs_prof_type { ...@@ -85,12 +89,19 @@ enum aafs_prof_type {
AAFS_PROF_MODE, AAFS_PROF_MODE,
AAFS_PROF_ATTACH, AAFS_PROF_ATTACH,
AAFS_PROF_HASH, AAFS_PROF_HASH,
AAFS_PROF_RAW_DATA,
AAFS_PROF_RAW_HASH,
AAFS_PROF_RAW_ABI,
AAFS_PROF_SIZEOF, AAFS_PROF_SIZEOF,
}; };
#define ns_dir(X) ((X)->dents[AAFS_NS_DIR]) #define ns_dir(X) ((X)->dents[AAFS_NS_DIR])
#define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS]) #define ns_subns_dir(X) ((X)->dents[AAFS_NS_NS])
#define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS]) #define ns_subprofs_dir(X) ((X)->dents[AAFS_NS_PROFS])
#define ns_subdata_dir(X) ((X)->dents[AAFS_NS_RAW_DATA])
#define ns_subload(X) ((X)->dents[AAFS_NS_LOAD])
#define ns_subreplace(X) ((X)->dents[AAFS_NS_REPLACE])
#define ns_subremove(X) ((X)->dents[AAFS_NS_REMOVE])
#define prof_dir(X) ((X)->dents[AAFS_PROF_DIR]) #define prof_dir(X) ((X)->dents[AAFS_PROF_DIR])
#define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS]) #define prof_child_dir(X) ((X)->dents[AAFS_PROF_PROFS])
...@@ -100,6 +111,7 @@ void __aa_fs_profile_migrate_dents(struct aa_profile *old, ...@@ -100,6 +111,7 @@ void __aa_fs_profile_migrate_dents(struct aa_profile *old,
struct aa_profile *new); struct aa_profile *new);
int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent); int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent);
void __aa_fs_ns_rmdir(struct aa_ns *ns); void __aa_fs_ns_rmdir(struct aa_ns *ns);
int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name); int __aa_fs_ns_mkdir(struct aa_ns *ns, struct dentry *parent, const char *name,
struct dentry *dent);
#endif /* __AA_APPARMORFS_H */ #endif /* __AA_APPARMORFS_H */
/*
* AppArmor security module
*
* This file contains AppArmor file mediation function definitions.
*
* Copyright 2014 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
*
* This is a file of helper macros, defines for backporting newer versions
* of apparmor to older kernels
*/
#ifndef __AA_BACKPORT_H
#define __AA_BACKPORT_H
#endif /* __AA_BACKPORT_H */
...@@ -18,9 +18,14 @@ ...@@ -18,9 +18,14 @@
#ifdef CONFIG_SECURITY_APPARMOR_HASH #ifdef CONFIG_SECURITY_APPARMOR_HASH
unsigned int aa_hash_size(void); 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, int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start,
size_t len); size_t len);
#else #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, static inline int aa_calc_profile_hash(struct aa_profile *profile, u32 version,
void *start, size_t len) void *start, size_t len)
{ {
......
...@@ -191,14 +191,15 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start, ...@@ -191,14 +191,15 @@ unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
const char *name, struct path_cond *cond, const char *name, struct path_cond *cond,
struct aa_perms *perms); struct aa_perms *perms);
int __aa_path_perm(const char *op, struct aa_profile *profile, const char *name, int __aa_path_perm(const char *op, struct aa_profile *profile,
u32 request, struct path_cond *cond, int flags, const char *name, u32 request, struct path_cond *cond,
struct aa_perms *perms); int flags, struct aa_perms *perms);
int aa_path_perm(const char *op, struct aa_label *label, struct path *path, int aa_path_perm(const char *op, struct aa_label *label,
int flags, u32 request, struct path_cond *cond); const struct path *path, int flags, u32 request,
struct path_cond *cond);
int aa_path_link(struct aa_label *label, struct dentry *old_dentry, int aa_path_link(struct aa_label *label, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry); const struct path *new_dir, struct dentry *new_dentry);
int aa_file_perm(const char *op, struct aa_label *label, struct file *file, int aa_file_perm(const char *op, struct aa_label *label, struct file *file,
u32 request); u32 request);
......
...@@ -29,26 +29,26 @@ ...@@ -29,26 +29,26 @@
#define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN) #define AA_MS_IGNORE_MASK (MS_KERNMOUNT | MS_NOSEC | MS_ACTIVE | MS_BORN)
int aa_remount(struct aa_label *label, struct path *path, unsigned long flags, int aa_remount(struct aa_label *label, const struct path *path,
void *data); unsigned long flags, void *data);
int aa_bind_mount(struct aa_label *label, struct path *path, int aa_bind_mount(struct aa_label *label, const struct path *path,
const char *old_name, unsigned long flags); const char *old_name, unsigned long flags);
int aa_mount_change_type(struct aa_label *label, struct path *path, int aa_mount_change_type(struct aa_label *label, const struct path *path,
unsigned long flags); unsigned long flags);
int aa_move_mount(struct aa_label *label, struct path *path, int aa_move_mount(struct aa_label *label, const struct path *path,
const char *old_name); const char *old_name);
int aa_new_mount(struct aa_label *label, const char *dev_name, int aa_new_mount(struct aa_label *label, const char *dev_name,
struct path *path, const char *type, unsigned long flags, const struct path *path, const char *type, unsigned long flags,
void *data); void *data);
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags); int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags);
int aa_pivotroot(struct aa_label *label, struct path *old_path, int aa_pivotroot(struct aa_label *label, const struct path *old_path,
struct path *new_path); const struct path *new_path);
#endif /* __AA_MOUNT_H */ #endif /* __AA_MOUNT_H */
...@@ -27,7 +27,7 @@ enum path_flags { ...@@ -27,7 +27,7 @@ enum path_flags {
PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */ PATH_MEDIATE_DELETED = 0x10000, /* mediate deleted paths */
}; };
int aa_path_name(struct path *path, int flags, char *buffer, int aa_path_name(const struct path *path, int flags, char *buffer,
const char **name, const char **info, const char *disconnect); const char **name, const char **info, const char *disconnect);
#define MAX_PATH_BUFFERS 2 #define MAX_PATH_BUFFERS 2
......
...@@ -104,14 +104,24 @@ extern struct aa_perms allperms; ...@@ -104,14 +104,24 @@ extern struct aa_perms allperms;
}) })
/* TODO: update for labels pointing to labels instead of profiles /*
* Note: this only works for profiles from a single namespace * TODO: update for labels pointing to labels instead of profiles
*/ * TODO: optimize the walk, currently does subwalk of L2 for each P in L1
* gah this doesn't allow for label compound check!!!!
*/
#define xcheck_ns_profile_profile(P1, P2, FN, args...) \
({ \
int ____e = 0; \
if (P1->ns == P2->ns) \
____e = FN((P1), (P2), args); \
(____e); \
})
#define xcheck_profile_label(P, L, FN, args...) \ #define xcheck_ns_profile_label(P, L, FN, args...) \
({ \ ({ \
struct aa_profile *__p2; \ struct aa_profile *__p2; \
fn_for_each((L), __p2, FN((P), __p2, args)); \ fn_for_each((L), __p2, \
xcheck_ns_profile_profile((P), __p2, (FN), args)); \
}) })
#define xcheck_ns_labels(L1, L2, FN, args...) \ #define xcheck_ns_labels(L1, L2, FN, args...) \
...@@ -120,13 +130,9 @@ extern struct aa_perms allperms; ...@@ -120,13 +130,9 @@ extern struct aa_perms allperms;
fn_for_each((L1), __p1, FN(__p1, (L2), args)); \ fn_for_each((L1), __p1, FN(__p1, (L2), args)); \
}) })
/* todo: fix to handle multiple namespaces */
#define xcheck_labels(L1, L2, FN, args...) \
xcheck_ns_labels((L1), (L2), FN, args)
/* Do the cross check but applying FN at the profiles level */ /* Do the cross check but applying FN at the profiles level */
#define xcheck_labels_profiles(L1, L2, FN, args...) \ #define xcheck_labels_profiles(L1, L2, FN, args...) \
xcheck_ns_labels((L1), (L2), xcheck_profile_label, (FN), args) xcheck_ns_labels((L1), (L2), xcheck_ns_profile_label, (FN), args)
#define FINAL_CHECK true #define FINAL_CHECK true
......
...@@ -18,6 +18,7 @@ ...@@ -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>
...@@ -78,6 +79,19 @@ struct aa_policydb { ...@@ -78,6 +79,19 @@ struct aa_policydb {
}; };
/* 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;
size_t 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 ...)
* @label - label this profile is an extension of * @label - label this profile is an extension of
...@@ -97,9 +111,9 @@ struct aa_policydb { ...@@ -97,9 +111,9 @@ struct aa_policydb {
* @caps: capabilities for the profile * @caps: capabilities for the profile
* @net: network controls for the profile * @net: network controls for the profile
* @rlimits: rlimits for the profile * @rlimits: rlimits for the profile
*
* @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
...@@ -135,9 +149,11 @@ struct aa_profile { ...@@ -135,9 +149,11 @@ struct aa_profile {
struct aa_net net; struct aa_net net;
struct aa_rlimit rlimits; struct aa_rlimit rlimits;
struct aa_loaddata *rawdata;
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;
struct aa_label label; struct aa_label label;
}; };
...@@ -169,9 +185,10 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base, ...@@ -169,9 +185,10 @@ struct aa_profile *aa_fqlookupn_profile(struct aa_label *base,
const char *fqname, size_t n); const char *fqname, size_t n);
struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name); struct aa_profile *aa_match_profile(struct aa_ns *ns, const char *name);
ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
size_t size); u32 mask, struct aa_loaddata *udata);
ssize_t aa_remove_profiles(struct aa_label *label, char *name, size_t size); ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
char *name, size_t size);
void __aa_profile_list_release(struct list_head *head); void __aa_profile_list_release(struct list_head *head);
#define PROF_ADD 1 #define PROF_ADD 1
...@@ -280,9 +297,9 @@ static inline int AUDIT_MODE(struct aa_profile *profile) ...@@ -280,9 +297,9 @@ static inline int AUDIT_MODE(struct aa_profile *profile)
return profile->audit; return profile->audit;
} }
bool policy_view_capable(void); bool policy_view_capable(struct aa_ns *ns);
bool policy_admin_capable(void); bool policy_admin_capable(struct aa_ns *ns);
bool aa_may_open_profiles(void); bool aa_may_open_profiles(void);
int aa_may_manage_policy(struct aa_label *label, u32 mask); int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask);
#endif /* __AA_POLICY_H */ #endif /* __AA_POLICY_H */
...@@ -88,6 +88,8 @@ void aa_free_ns_kref(struct kref *kref); ...@@ -88,6 +88,8 @@ void aa_free_ns_kref(struct kref *kref);
struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name); struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name);
struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n); struct aa_ns *aa_findn_ns(struct aa_ns *root, const char *name, size_t n);
struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir);
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name); struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name);
void __aa_remove_ns(struct aa_ns *ns); void __aa_remove_ns(struct aa_ns *ns);
...@@ -124,4 +126,25 @@ static inline void aa_put_ns(struct aa_ns *ns) ...@@ -124,4 +126,25 @@ static inline void aa_put_ns(struct aa_ns *ns)
aa_put_profile(ns->unconfined); aa_put_profile(ns->unconfined);
} }
/**
* __aa_findn_ns - find a namespace on a list by @name
* @head: list to search for namespace on (NOT NULL)
* @name: name of namespace to look for (NOT NULL)
* @n: length of @name
* Returns: unrefcounted namespace
*
* Requires: rcu_read_lock be held
*/
static inline struct aa_ns *__aa_findn_ns(struct list_head *head,
const char *name, size_t n)
{
return (struct aa_ns *)__policy_strn_find(head, name, n);
}
static inline struct aa_ns *__aa_find_ns(struct list_head *head,
const char *name)
{
return __aa_findn_ns(head, name, strlen(name));
}
#endif /* AA_NAMESPACE_H */ #endif /* AA_NAMESPACE_H */
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#define __POLICY_INTERFACE_H #define __POLICY_INTERFACE_H
#include <linux/list.h> #include <linux/list.h>
#include <linux/kref.h>
struct aa_load_ent { struct aa_load_ent {
struct list_head list; struct list_head list;
...@@ -35,6 +36,30 @@ struct aa_load_ent *aa_load_ent_alloc(void); ...@@ -35,6 +36,30 @@ struct aa_load_ent *aa_load_ent_alloc(void);
#define PACKED_MODE_KILL 2 #define PACKED_MODE_KILL 2
#define PACKED_MODE_UNCONFINED 3 #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 */ #endif /* __POLICY_INTERFACE_H */
...@@ -1061,8 +1061,11 @@ static struct aa_label *label_merge_insert(struct aa_label *new, ...@@ -1061,8 +1061,11 @@ static struct aa_label *label_merge_insert(struct aa_label *new,
AA_BUG(new->size < a->size + b->size); AA_BUG(new->size < a->size + b->size);
label_for_each_in_merge(i, a, b, next) { label_for_each_in_merge(i, a, b, next) {
AA_BUG(!next);
if (profile_is_stale(next)) { if (profile_is_stale(next)) {
new->vec[k] = aa_get_newest_profile(next); new->vec[k] = aa_get_newest_profile(next);
AA_BUG(!new->vec[k]->label.proxy);
AA_BUG(!new->vec[k]->label.proxy->label);
if (next->label.proxy != new->vec[k]->label.proxy) if (next->label.proxy != new->vec[k]->label.proxy)
invcount++; invcount++;
k++; k++;
...@@ -1991,10 +1994,8 @@ static struct aa_label *__label_update(struct aa_label *label) ...@@ -1991,10 +1994,8 @@ static struct aa_label *__label_update(struct aa_label *label)
{ {
struct aa_label *new, *tmp; struct aa_label *new, *tmp;
struct aa_labelset *ls; struct aa_labelset *ls;
struct aa_profile *p;
struct label_it i;
unsigned long flags; unsigned long flags;
int invcount = 0; int i, invcount = 0;
AA_BUG(!label); AA_BUG(!label);
AA_BUG(!mutex_is_locked(&labels_ns(label)->lock)); AA_BUG(!mutex_is_locked(&labels_ns(label)->lock));
...@@ -2008,9 +2009,13 @@ static struct aa_label *__label_update(struct aa_label *label) ...@@ -2008,9 +2009,13 @@ static struct aa_label *__label_update(struct aa_label *label)
*/ */
ls = labels_set(label); ls = labels_set(label);
write_lock_irqsave(&ls->lock, flags); write_lock_irqsave(&ls->lock, flags);
label_for_each(i, label, p) { for (i = 0; i < label->size; i++) {
new->vec[i.i] = aa_get_newest_profile(p); AA_BUG(!label->vec[i]);
if (new->vec[i.i]->label.proxy != p->label.proxy) new->vec[i] = aa_get_newest_profile(label->vec[i]);
AA_BUG(!new->vec[i]);
AA_BUG(!new->vec[i]->label.proxy);
AA_BUG(!new->vec[i]->label.proxy->label);
if (new->vec[i]->label.proxy != label->vec[i]->label.proxy)
invcount++; invcount++;
} }
......
...@@ -186,7 +186,7 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, ...@@ -186,7 +186,7 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm(const char *op, struct path *path, u32 mask, static int common_perm(const char *op, const struct path *path, u32 mask,
struct path_cond *cond) struct path_cond *cond)
{ {
struct aa_label *label; struct aa_label *label;
...@@ -200,12 +200,15 @@ static int common_perm(const char *op, struct path *path, u32 mask, ...@@ -200,12 +200,15 @@ static int common_perm(const char *op, struct path *path, u32 mask,
return error; return error;
} }
static int common_perm_cond(const char *op, struct path *path, u32 mask) static int common_perm_cond(const char *op, const struct path *path, u32 mask)
{ {
struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, struct path_cond cond = { d_backing_inode(path->dentry)->i_uid,
d_backing_inode(path->dentry)->i_mode d_backing_inode(path->dentry)->i_mode
}; };
if (!path_mediated_fs(path->dentry))
return 0;
return common_perm(op, path, mask, &cond); return common_perm(op, path, mask, &cond);
} }
...@@ -229,7 +232,7 @@ static void apparmor_inode_free_security(struct inode *inode) ...@@ -229,7 +232,7 @@ static void apparmor_inode_free_security(struct inode *inode)
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_dir_dentry(const char *op, struct path *dir, static int common_perm_dir_dentry(const char *op, const struct path *dir,
struct dentry *dentry, u32 mask, struct dentry *dentry, u32 mask,
struct path_cond *cond) struct path_cond *cond)
{ {
...@@ -238,23 +241,6 @@ static int common_perm_dir_dentry(const char *op, struct path *dir, ...@@ -238,23 +241,6 @@ static int common_perm_dir_dentry(const char *op, struct path *dir,
return common_perm(op, &path, mask, cond); return common_perm(op, &path, mask, cond);
} }
/**
* common_perm_mnt_dentry - common permission wrapper when mnt, dentry
* @op: operation being checked
* @mnt: mount point of dentry (NOT NULL)
* @dentry: dentry to check (NOT NULL)
* @mask: requested permissions mask
*
* Returns: %0 else error code if error or permission denied
*/
static int common_perm_mnt_dentry(const char *op, struct vfsmount *mnt,
struct dentry *dentry, u32 mask)
{
struct path path = { mnt, dentry };
return common_perm_cond(op, &path, mask);
}
/** /**
* common_perm_rm - common permission wrapper for operations doing rm * common_perm_rm - common permission wrapper for operations doing rm
* @op: operation being checked * @op: operation being checked
...@@ -264,13 +250,13 @@ static int common_perm_mnt_dentry(const char *op, struct vfsmount *mnt, ...@@ -264,13 +250,13 @@ static int common_perm_mnt_dentry(const char *op, struct vfsmount *mnt,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_rm(const char *op, struct path *dir, static int common_perm_rm(const char *op, const struct path *dir,
struct dentry *dentry, u32 mask) struct dentry *dentry, u32 mask)
{ {
struct inode *inode = d_backing_inode(dentry); struct inode *inode = d_backing_inode(dentry);
struct path_cond cond = { }; struct path_cond cond = { };
if (!inode || !dir->mnt || !path_mediated_fs(dentry)) if (!inode || !path_mediated_fs(dentry))
return 0; return 0;
cond.uid = inode->i_uid; cond.uid = inode->i_uid;
...@@ -289,56 +275,53 @@ static int common_perm_rm(const char *op, struct path *dir, ...@@ -289,56 +275,53 @@ static int common_perm_rm(const char *op, struct path *dir,
* *
* Returns: %0 else error code if error or permission denied * Returns: %0 else error code if error or permission denied
*/ */
static int common_perm_create(const char *op, struct path *dir, struct dentry *dentry, static int common_perm_create(const char *op, const struct path *dir,
u32 mask, umode_t mode) struct dentry *dentry, u32 mask, umode_t mode)
{ {
struct path_cond cond = { current_fsuid(), mode }; struct path_cond cond = { current_fsuid(), mode };
if (!dir->mnt || !path_mediated_fs(dir->dentry)) if (!path_mediated_fs(dir->dentry))
return 0; return 0;
return common_perm_dir_dentry(op, dir, dentry, mask, &cond); return common_perm_dir_dentry(op, dir, dentry, mask, &cond);
} }
static int apparmor_path_unlink(struct path *dir, struct dentry *dentry) static int apparmor_path_unlink(const struct path *dir, struct dentry *dentry)
{ {
return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE); return common_perm_rm(OP_UNLINK, dir, dentry, AA_MAY_DELETE);
} }
static int apparmor_path_mkdir(struct path *dir, struct dentry *dentry, static int apparmor_path_mkdir(const struct path *dir, struct dentry *dentry,
umode_t mode) umode_t mode)
{ {
return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE, return common_perm_create(OP_MKDIR, dir, dentry, AA_MAY_CREATE,
S_IFDIR); S_IFDIR);
} }
static int apparmor_path_rmdir(struct path *dir, struct dentry *dentry) static int apparmor_path_rmdir(const struct path *dir, struct dentry *dentry)
{ {
return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE); return common_perm_rm(OP_RMDIR, dir, dentry, AA_MAY_DELETE);
} }
static int apparmor_path_mknod(struct path *dir, struct dentry *dentry, static int apparmor_path_mknod(const struct path *dir, struct dentry *dentry,
umode_t mode, unsigned int dev) umode_t mode, unsigned int dev)
{ {
return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode); return common_perm_create(OP_MKNOD, dir, dentry, AA_MAY_CREATE, mode);
} }
static int apparmor_path_truncate(struct path *path) static int apparmor_path_truncate(const struct path *path)
{ {
if (!path->mnt || !path_mediated_fs(path->dentry))
return 0;
return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR);
} }
static int apparmor_path_symlink(struct path *dir, struct dentry *dentry, static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry,
const char *old_name) const char *old_name)
{ {
return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE, return common_perm_create(OP_SYMLINK, dir, dentry, AA_MAY_CREATE,
S_IFLNK); S_IFLNK);
} }
static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, static int apparmor_path_link(struct dentry *old_dentry, const struct path *new_dir,
struct dentry *new_dentry) struct dentry *new_dentry)
{ {
struct aa_label *label; struct aa_label *label;
...@@ -355,8 +338,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir, ...@@ -355,8 +338,8 @@ static int apparmor_path_link(struct dentry *old_dentry, struct path *new_dir,
return error; return error;
} }
static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry, static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_dentry,
struct path *new_dir, struct dentry *new_dentry) const struct path *new_dir, struct dentry *new_dentry)
{ {
struct aa_label *label; struct aa_label *label;
int error = 0; int error = 0;
...@@ -387,29 +370,19 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry, ...@@ -387,29 +370,19 @@ static int apparmor_path_rename(struct path *old_dir, struct dentry *old_dentry,
return error; return error;
} }
static int apparmor_path_chmod(struct path *path, umode_t mode) static int apparmor_path_chmod(const struct path *path, umode_t mode)
{ {
if (!path_mediated_fs(path->dentry))
return 0;
return common_perm_cond(OP_CHMOD, path, AA_MAY_CHMOD); return common_perm_cond(OP_CHMOD, path, AA_MAY_CHMOD);
} }
static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid) static int apparmor_path_chown(const struct path *path, kuid_t uid, kgid_t gid)
{ {
if (!path_mediated_fs(path->dentry))
return 0;
return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN); return common_perm_cond(OP_CHOWN, path, AA_MAY_CHOWN);
} }
static int apparmor_inode_getattr(const struct path *path) static int apparmor_inode_getattr(const struct path *path)
{ {
if (!path_mediated_fs(path->dentry)) return common_perm_cond(OP_GETATTR, path, AA_MAY_GETATTR);
return 0;
return common_perm_mnt_dentry(OP_GETATTR, path->mnt, path->dentry,
AA_MAY_GETATTR);
} }
static int apparmor_file_open(struct file *file, const struct cred *cred) static int apparmor_file_open(struct file *file, const struct cred *cred)
...@@ -532,7 +505,7 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma, ...@@ -532,7 +505,7 @@ static int apparmor_file_mprotect(struct vm_area_struct *vma,
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0); !(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
} }
static int apparmor_sb_mount(const char *dev_name, struct path *path, static int apparmor_sb_mount(const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data) const char *type, unsigned long flags, void *data)
{ {
struct aa_label *label; struct aa_label *label;
...@@ -577,7 +550,8 @@ static int apparmor_sb_umount(struct vfsmount *mnt, int flags) ...@@ -577,7 +550,8 @@ static int apparmor_sb_umount(struct vfsmount *mnt, int flags)
return error; return error;
} }
static int apparmor_sb_pivotroot(struct path *old_path, struct path *new_path) static int apparmor_sb_pivotroot(const struct path *old_path,
const struct path *new_path)
{ {
struct aa_label *label; struct aa_label *label;
int error = 0; int error = 0;
...@@ -620,37 +594,37 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, ...@@ -620,37 +594,37 @@ static int apparmor_getprocattr(struct task_struct *task, char *name,
static int apparmor_setprocattr(struct task_struct *task, char *name, static int apparmor_setprocattr(struct task_struct *task, char *name,
void *value, size_t size) void *value, size_t size)
{ {
char *command, *args = value; char *command, *largs = NULL, *args = value;
size_t arg_size; size_t arg_size;
int error; int error;
DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR); DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, OP_SETPROCATTR);
if (size == 0) if (size == 0)
return -EINVAL; return -EINVAL;
/* args points to a PAGE_SIZE buffer, AppArmor requires that
* the buffer must be null terminated or have size <= PAGE_SIZE -1
* so that AppArmor can null terminate them
*/
if (args[size - 1] != '\0') {
if (size == PAGE_SIZE)
return -EINVAL;
args[size] = '\0';
}
/* task can only write its own attributes */ /* task can only write its own attributes */
if (current != task) if (current != task)
return -EACCES; return -EACCES;
args = value; /* AppArmor requires that the buffer must be null terminated atm */
if (args[size - 1] != '\0') {
/* null terminate */
largs = args = kmalloc(size + 1, GFP_KERNEL);
if (!args)
return -ENOMEM;
memcpy(args, value, size);
args[size] = '\0';
}
error = -EINVAL;
args = strim(args); args = strim(args);
command = strsep(&args, " "); command = strsep(&args, " ");
if (!args) if (!args)
return -EINVAL; goto out;
args = skip_spaces(args); args = skip_spaces(args);
if (!*args) if (!*args)
return -EINVAL; goto out;
arg_size = size - (args - (char *) value); arg_size = size - (args - (largs ? largs : (char *) value));
if (strcmp(name, "current") == 0) { if (strcmp(name, "current") == 0) {
if (strcmp(command, "changehat") == 0) { if (strcmp(command, "changehat") == 0) {
error = aa_setprocattr_changehat(args, arg_size, error = aa_setprocattr_changehat(args, arg_size,
...@@ -680,19 +654,21 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, ...@@ -680,19 +654,21 @@ static int apparmor_setprocattr(struct task_struct *task, char *name,
goto fail; goto fail;
} else } else
/* only support the "current" and "exec" process attributes */ /* only support the "current" and "exec" process attributes */
return -EINVAL; goto fail;
if (!error) if (!error)
error = size; error = size;
out:
kfree(largs);
return error; return error;
fail: fail:
aad(&sa)->label = aa_begin_current_label(DO_UPDATE); aad(&sa)->label = aa_begin_current_label(DO_UPDATE);
aad(&sa)->info = name; aad(&sa)->info = name;
aad(&sa)->error = -EINVAL; aad(&sa)->error = error = -EINVAL;
aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL); aa_audit_msg(AUDIT_APPARMOR_DENIED, &sa, NULL);
aa_end_current_label(aad(&sa)->label); aa_end_current_label(aad(&sa)->label);
return -EINVAL; goto out;
} }
/** /**
...@@ -1164,6 +1140,78 @@ static int apparmor_task_kill(struct task_struct *target, struct siginfo *info, ...@@ -1164,6 +1140,78 @@ static int apparmor_task_kill(struct task_struct *target, struct siginfo *info,
return error; return error;
} }
/* 4.6 backport wrappers to keep constification of struct path */
static int wrap_apparmor_sb_mount(const char *dev_name, struct path *path,
const char *type, unsigned long flags,
void *data)
{
return apparmor_sb_mount(dev_name, path, type, flags, data);
}
static int wrap_apparmor_sb_pivotroot(struct path *old_path,
struct path *new_path)
{
return apparmor_sb_pivotroot(old_path, new_path);
}
static int wrap_apparmor_path_link(struct dentry *old_dentry,
struct path *new_dir,
struct dentry *new_dentry)
{
return apparmor_path_link(old_dentry, new_dir, new_dentry);
}
static int wrap_apparmor_path_unlink(struct path *dir, struct dentry *dentry)
{
return apparmor_path_unlink(dir, dentry);
}
static int wrap_apparmor_path_symlink(struct path *dir, struct dentry *dentry,
const char *old_name)
{
return apparmor_path_symlink(dir, dentry, old_name);
}
static int wrap_apparmor_path_mkdir(struct path *dir, struct dentry *dentry,
umode_t mode)
{
return apparmor_path_mkdir(dir, dentry, mode);
}
static int wrap_apparmor_path_rmdir(struct path *dir, struct dentry *dentry)
{
return apparmor_path_rmdir(dir, dentry);
}
static int wrap_apparmor_path_mknod(struct path *dir, struct dentry *dentry,
umode_t mode, unsigned int dev)
{
return apparmor_path_mknod(dir, dentry, mode, dev);
}
static int wrap_apparmor_path_rename(struct path *old_dir,
struct dentry *old_dentry,
struct path *new_dir,
struct dentry *new_dentry)
{
return apparmor_path_rename(old_dir, old_dentry, new_dir, new_dentry);
}
static int wrap_apparmor_path_chmod(struct path *path, umode_t mode)
{
return apparmor_path_chmod(path, mode);
}
static int wrap_apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid)
{
return apparmor_path_chown(path, uid, gid);
}
static int wrap_apparmor_path_truncate(struct path *path)
{
return apparmor_path_truncate(path);
}
#ifndef LSM_HOOKS_NAME #ifndef LSM_HOOKS_NAME
#define LSM_HOOKS_NAME(X) //.name = (X), #define LSM_HOOKS_NAME(X) //.name = (X),
#endif #endif
...@@ -1177,20 +1225,20 @@ static struct security_hook_list apparmor_hooks[] = { ...@@ -1177,20 +1225,20 @@ static struct security_hook_list apparmor_hooks[] = {
LSM_HOOK_INIT(inode_free_security, apparmor_inode_free_security), LSM_HOOK_INIT(inode_free_security, apparmor_inode_free_security),
LSM_HOOK_INIT(sb_mount, apparmor_sb_mount), LSM_HOOK_INIT(sb_mount, wrap_apparmor_sb_mount),
LSM_HOOK_INIT(sb_umount, apparmor_sb_umount), LSM_HOOK_INIT(sb_umount, apparmor_sb_umount),
LSM_HOOK_INIT(sb_pivotroot, apparmor_sb_pivotroot), LSM_HOOK_INIT(sb_pivotroot, wrap_apparmor_sb_pivotroot),
LSM_HOOK_INIT(path_link, apparmor_path_link), LSM_HOOK_INIT(path_link, wrap_apparmor_path_link),
LSM_HOOK_INIT(path_unlink, apparmor_path_unlink), LSM_HOOK_INIT(path_unlink, wrap_apparmor_path_unlink),
LSM_HOOK_INIT(path_symlink, apparmor_path_symlink), LSM_HOOK_INIT(path_symlink, wrap_apparmor_path_symlink),
LSM_HOOK_INIT(path_mkdir, apparmor_path_mkdir), LSM_HOOK_INIT(path_mkdir, wrap_apparmor_path_mkdir),
LSM_HOOK_INIT(path_rmdir, apparmor_path_rmdir), LSM_HOOK_INIT(path_rmdir, wrap_apparmor_path_rmdir),
LSM_HOOK_INIT(path_mknod, apparmor_path_mknod), LSM_HOOK_INIT(path_mknod, wrap_apparmor_path_mknod),
LSM_HOOK_INIT(path_rename, apparmor_path_rename), LSM_HOOK_INIT(path_rename, wrap_apparmor_path_rename),
LSM_HOOK_INIT(path_chmod, apparmor_path_chmod), LSM_HOOK_INIT(path_chmod, wrap_apparmor_path_chmod),
LSM_HOOK_INIT(path_chown, apparmor_path_chown), LSM_HOOK_INIT(path_chown, wrap_apparmor_path_chown),
LSM_HOOK_INIT(path_truncate, apparmor_path_truncate), LSM_HOOK_INIT(path_truncate, wrap_apparmor_path_truncate),
LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr), LSM_HOOK_INIT(inode_getattr, apparmor_inode_getattr),
LSM_HOOK_INIT(file_open, apparmor_file_open), LSM_HOOK_INIT(file_open, apparmor_file_open),
...@@ -1290,9 +1338,11 @@ enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE; ...@@ -1290,9 +1338,11 @@ enum profile_mode aa_g_profile_mode = APPARMOR_ENFORCE;
module_param_call(mode, param_set_mode, param_get_mode, module_param_call(mode, param_set_mode, param_get_mode,
&aa_g_profile_mode, S_IRUGO | S_IWUSR); &aa_g_profile_mode, S_IRUGO | S_IWUSR);
#ifdef CONFIG_SECURITY_APPARMOR_HASH
/* whether policy verification hashing is enabled */ /* whether policy verification hashing is enabled */
bool aa_g_hash_policy = CONFIG_SECURITY_APPARMOR_HASH_DEFAULT; bool aa_g_hash_policy = IS_ENABLED(CONFIG_SECURITY_APPARMOR_HASH_DEFAULT);
module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUGO | S_IWUSR); module_param_named(hash_policy, aa_g_hash_policy, aabool, S_IRUSR | S_IWUSR);
#endif
/* Debug mode */ /* Debug mode */
bool aa_g_debug; bool aa_g_debug;
...@@ -1356,14 +1406,14 @@ __setup("apparmor=", apparmor_enabled_setup); ...@@ -1356,14 +1406,14 @@ __setup("apparmor=", apparmor_enabled_setup);
/* set global flag turning off the ability to load policy */ /* set global flag turning off the ability to load policy */
static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp) static int param_set_aalockpolicy(const char *val, const struct kernel_param *kp)
{ {
if (!policy_admin_capable()) if (!policy_admin_capable(NULL))
return -EPERM; return -EPERM;
return param_set_bool(val, kp); return param_set_bool(val, kp);
} }
static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp)
{ {
if (!policy_view_capable()) if (!policy_view_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1372,7 +1422,7 @@ static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp) ...@@ -1372,7 +1422,7 @@ static int param_get_aalockpolicy(char *buffer, const struct kernel_param *kp)
static int param_set_aabool(const char *val, const struct kernel_param *kp) static int param_set_aabool(const char *val, const struct kernel_param *kp)
{ {
if (!policy_admin_capable()) if (!policy_admin_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1381,7 +1431,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp) ...@@ -1381,7 +1431,7 @@ static int param_set_aabool(const char *val, const struct kernel_param *kp)
static int param_get_aabool(char *buffer, const struct kernel_param *kp) static int param_get_aabool(char *buffer, const struct kernel_param *kp)
{ {
if (!policy_view_capable()) if (!policy_view_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1390,7 +1440,7 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp) ...@@ -1390,7 +1440,7 @@ static int param_get_aabool(char *buffer, const struct kernel_param *kp)
static int param_set_aauint(const char *val, const struct kernel_param *kp) static int param_set_aauint(const char *val, const struct kernel_param *kp)
{ {
if (!policy_admin_capable()) if (!policy_admin_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1399,7 +1449,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp) ...@@ -1399,7 +1449,7 @@ static int param_set_aauint(const char *val, const struct kernel_param *kp)
static int param_get_aauint(char *buffer, const struct kernel_param *kp) static int param_get_aauint(char *buffer, const struct kernel_param *kp)
{ {
if (!policy_view_capable()) if (!policy_view_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1408,7 +1458,7 @@ static int param_get_aauint(char *buffer, const struct kernel_param *kp) ...@@ -1408,7 +1458,7 @@ static int param_get_aauint(char *buffer, const struct kernel_param *kp)
static int param_get_audit(char *buffer, struct kernel_param *kp) static int param_get_audit(char *buffer, struct kernel_param *kp)
{ {
if (!policy_view_capable()) if (!policy_view_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1418,7 +1468,7 @@ static int param_get_audit(char *buffer, struct kernel_param *kp) ...@@ -1418,7 +1468,7 @@ static int param_get_audit(char *buffer, struct kernel_param *kp)
static int param_set_audit(const char *val, struct kernel_param *kp) static int param_set_audit(const char *val, struct kernel_param *kp)
{ {
int i; int i;
if (!policy_admin_capable()) if (!policy_admin_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1437,7 +1487,7 @@ static int param_set_audit(const char *val, struct kernel_param *kp) ...@@ -1437,7 +1487,7 @@ static int param_set_audit(const char *val, struct kernel_param *kp)
static int param_get_mode(char *buffer, struct kernel_param *kp) static int param_get_mode(char *buffer, struct kernel_param *kp)
{ {
if (!policy_view_capable()) if (!policy_view_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1448,7 +1498,7 @@ static int param_get_mode(char *buffer, struct kernel_param *kp) ...@@ -1448,7 +1498,7 @@ static int param_get_mode(char *buffer, struct kernel_param *kp)
static int param_set_mode(const char *val, struct kernel_param *kp) static int param_set_mode(const char *val, struct kernel_param *kp)
{ {
int i; int i;
if (!policy_admin_capable()) if (!policy_admin_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
...@@ -1535,7 +1585,7 @@ static int __init alloc_buffers(void) ...@@ -1535,7 +1585,7 @@ static int __init alloc_buffers(void)
static int apparmor_dointvec(struct ctl_table *table, int write, static int apparmor_dointvec(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos) void __user *buffer, size_t *lenp, loff_t *ppos)
{ {
if (!policy_admin_capable()) if (!policy_admin_capable(NULL))
return -EPERM; return -EPERM;
if (!apparmor_enabled) if (!apparmor_enabled)
return -EINVAL; return -EINVAL;
......
...@@ -90,7 +90,9 @@ static struct table_header *unpack_table(char *blob, size_t bsize) ...@@ -90,7 +90,9 @@ static struct table_header *unpack_table(char *blob, size_t bsize)
table = kvzalloc(tsize); table = kvzalloc(tsize);
if (table) { if (table) {
*table = th; table->td_id = th.td_id;
table->td_flags = th.td_flags;
table->td_lolen = th.td_lolen;
if (th.td_flags == YYTD_DATA8) if (th.td_flags == YYTD_DATA8)
UNPACK_ARRAY(table->td_data, blob, th.td_lolen, UNPACK_ARRAY(table->td_data, blob, th.td_lolen,
u8, byte_to_byte); u8, byte_to_byte);
......
...@@ -292,30 +292,54 @@ static int do_match_mnt(struct aa_dfa *dfa, unsigned int start, ...@@ -292,30 +292,54 @@ static int do_match_mnt(struct aa_dfa *dfa, unsigned int start,
return 4; return 4;
} }
static int path_flags(struct aa_profile *profile, const struct path *path)
{
AA_BUG(!profile);
AA_BUG(!path);
return profile->path_flags |
(S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0);
}
/** /**
* match_mnt - handle path matching for mount * match_mnt_path_str - handle path matching for mount
* @profile: the confining profile * @profile: the confining profile
* @mntpnt: string for the mntpnt (NOT NULL) * @mntpath: for the mntpnt (NOT NULL)
* @devname: string for the devname/src_name (MAYBE NULL) * @buffer: buffer to be used to lookup mntpath
* @devnme: string for the devname/src_name (MAY BE NULL OR ERRPTR)
* @type: string for the dev type (MAYBE NULL) * @type: string for the dev type (MAYBE NULL)
* @flags: mount flags to match * @flags: mount flags to match
* @data: fs mount data (MAYBE NULL) * @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary * @binary: whether @data is binary
* @perms: Returns: permission found by the match * @devinfo: error str if (IS_ERR(@devname))
* @info: Returns: infomation string about the match for logging
* *
* Returns: 0 on success else error * Returns: 0 on success else error
*/ */
static int match_mnt(struct aa_profile *profile, const char *mntpnt, static int match_mnt_path_str(struct aa_profile *profile, const struct path *mntpath,
const char *devname, const char *type, char *buffer, const char *devname,
unsigned long flags, void *data, bool binary) const char *type, unsigned long flags,
void *data, bool binary, const char *devinfo)
{ {
struct aa_perms perms = { }; struct aa_perms perms = { };
const char *info = NULL; const char *mntpnt = NULL, *info = NULL;
int pos, error = -EACCES; int pos, error;
AA_BUG(!profile); AA_BUG(!profile);
AA_BUG(!mntpath);
AA_BUG(!buffer);
error = aa_path_name(mntpath, path_flags(profile, mntpath), buffer,
&mntpnt, &info, profile->disconnected);
if (error)
goto audit;
if (IS_ERR(devname)) {
error = PTR_ERR(devname);
info = devinfo;
goto audit;
}
error = -EACCES;
pos = do_match_mnt(profile->policy.dfa, pos = do_match_mnt(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT], profile->policy.start[AA_CLASS_MOUNT],
mntpnt, devname, type, flags, data, binary, &perms); mntpnt, devname, type, flags, data, binary, &perms);
...@@ -330,20 +354,47 @@ static int match_mnt(struct aa_profile *profile, const char *mntpnt, ...@@ -330,20 +354,47 @@ static int match_mnt(struct aa_profile *profile, const char *mntpnt,
flags, data, AA_MAY_MOUNT, &perms, info, error); flags, data, AA_MAY_MOUNT, &perms, info, error);
} }
static int path_flags(struct aa_profile *profile, struct path *path) /**
* match_mnt - handle path matching for mount
* @profile: the confining profile
* @mntpath: for the mntpnt (NOT NULL)
* @buffer: buffer to be used to lookup mntpath
* @devpath: path devname/src_name (MAYBE NULL)
* @devbuffer: buffer to be used to lookup devname/src_name
* @type: string for the dev type (MAYBE NULL)
* @flags: mount flags to match
* @data: fs mount data (MAYBE NULL)
* @binary: whether @data is binary
*
* Returns: 0 on success else error
*/
static int match_mnt(struct aa_profile *profile, const struct path *path,
char *buffer, struct path *devpath, char *devbuffer,
const char *type, unsigned long flags, void *data,
bool binary)
{ {
const char *devname = NULL, *info = NULL;
int error = -EACCES;
AA_BUG(!profile); AA_BUG(!profile);
AA_BUG(!path); AA_BUG(devpath && !devbuffer);
return profile->path_flags | if (devpath) {
(S_ISDIR(path->dentry->d_inode->i_mode) ? PATH_IS_DIR : 0); error = aa_path_name(devpath, path_flags(profile, devpath),
devbuffer, &devname, &info,
profile->disconnected);
if (error)
devname = ERR_PTR(error);
}
return match_mnt_path_str(profile, path, buffer, devname, type, flags,
data, binary, info);
} }
int aa_remount(struct aa_label *label, struct path *path, unsigned long flags, int aa_remount(struct aa_label *label, const struct path *path,
void *data) unsigned long flags, void *data)
{ {
struct aa_profile *profile; struct aa_profile *profile;
const char *name, *info = NULL;
char *buffer = NULL; char *buffer = NULL;
bool binary; bool binary;
int error; int error;
...@@ -354,32 +405,19 @@ int aa_remount(struct aa_label *label, struct path *path, unsigned long flags, ...@@ -354,32 +405,19 @@ int aa_remount(struct aa_label *label, struct path *path, unsigned long flags,
binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA; binary = path->dentry->d_sb->s_type->fs_flags & FS_BINARY_MOUNTDATA;
get_buffers(buffer); get_buffers(buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error) {
error = audit_mount(labels_profile(label), OP_MOUNT, name, NULL,
NULL, NULL, flags, data, AA_MAY_MOUNT,
&nullperms, info, error);
goto out;
}
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
match_mnt(profile, name, NULL, NULL, flags, data, match_mnt(profile, path, buffer, NULL, NULL, NULL,
binary)); flags, data, binary));
out:
put_buffers(buffer); put_buffers(buffer);
return error; return error;
} }
int aa_bind_mount(struct aa_label *label, struct path *path, int aa_bind_mount(struct aa_label *label, const struct path *path,
const char *dev_name, unsigned long flags) const char *dev_name, unsigned long flags)
{ {
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL, *old_buffer = NULL; char *buffer = NULL, *old_buffer = NULL;
const char *name, *old_name = NULL, *info = NULL;
struct path old_path; struct path old_path;
int error; int error;
...@@ -396,42 +434,20 @@ int aa_bind_mount(struct aa_label *label, struct path *path, ...@@ -396,42 +434,20 @@ int aa_bind_mount(struct aa_label *label, struct path *path,
return error; return error;
get_buffers(buffer, old_buffer); get_buffers(buffer, old_buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path), buffer, &name,
&info, labels_profile(label)->disconnected);
if (error)
goto error;
error = aa_path_name(&old_path, path_flags(labels_profile(label),
&old_path),
old_buffer, &old_name, &info,
labels_profile(label)->disconnected);
path_put(&old_path);
if (error)
goto error;
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
match_mnt(profile, name, old_name, NULL, flags, NULL, match_mnt(profile, path, buffer, &old_path, old_buffer,
false)); NULL, flags, NULL, false));
out:
put_buffers(buffer, old_buffer); put_buffers(buffer, old_buffer);
path_put(&old_path);
return error; return error;
error:
error = fn_for_each(label, profile,
audit_mount(profile, OP_MOUNT, name, old_name, NULL,
NULL, flags, NULL, AA_MAY_MOUNT, &nullperms,
info, error));
goto out;
} }
int aa_mount_change_type(struct aa_label *label, struct path *path, int aa_mount_change_type(struct aa_label *label, const struct path *path,
unsigned long flags) unsigned long flags)
{ {
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL; char *buffer = NULL;
const char *name, *info = NULL;
int error; int error;
AA_BUG(!label); AA_BUG(!label);
...@@ -442,34 +458,19 @@ int aa_mount_change_type(struct aa_label *label, struct path *path, ...@@ -442,34 +458,19 @@ int aa_mount_change_type(struct aa_label *label, struct path *path,
MS_UNBINDABLE); MS_UNBINDABLE);
get_buffers(buffer); get_buffers(buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error) {
error = fn_for_each(label, profile,
audit_mount(profile, OP_MOUNT, name, NULL,
NULL, NULL, flags, NULL,
AA_MAY_MOUNT, &nullperms, info,
error));
goto out;
}
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
match_mnt(profile, name, NULL, NULL, flags, NULL, match_mnt(profile, path, buffer, NULL, NULL, NULL,
false)); flags, NULL, false));
out:
put_buffers(buffer); put_buffers(buffer);
return error; return error;
} }
int aa_move_mount(struct aa_label *label, struct path *path, int aa_move_mount(struct aa_label *label, const struct path *path,
const char *orig_name) const char *orig_name)
{ {
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL, *old_buffer = NULL; char *buffer = NULL, *old_buffer = NULL;
const char *name, *old_name = NULL, *info = NULL;
struct path old_path; struct path old_path;
int error; int error;
...@@ -484,53 +485,29 @@ int aa_move_mount(struct aa_label *label, struct path *path, ...@@ -484,53 +485,29 @@ int aa_move_mount(struct aa_label *label, struct path *path,
return error; return error;
get_buffers(buffer, old_buffer); get_buffers(buffer, old_buffer);
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error)
goto error;
error = aa_path_name(&old_path, path_flags(labels_profile(label),
&old_path),
old_buffer, &old_name, &info,
labels_profile(label)->disconnected);
path_put(&old_path);
if (error)
goto error;
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
match_mnt(profile, name, old_name, NULL, MS_MOVE, NULL, match_mnt(profile, path, buffer, &old_path, old_buffer,
false)); NULL, MS_MOVE, NULL, false));
out:
put_buffers(buffer, old_buffer); put_buffers(buffer, old_buffer);
path_put(&old_path);
return error; return error;
error:
error = fn_for_each(label, profile,
audit_mount(profile, OP_MOUNT, name, old_name, NULL,
NULL, MS_MOVE, NULL, AA_MAY_MOUNT,
&nullperms, info, error));
goto out;
} }
int aa_new_mount(struct aa_label *label, const char *orig_dev_name, int aa_new_mount(struct aa_label *label, const char *dev_name,
struct path *path, const char *type, unsigned long flags, const struct path *path, const char *type, unsigned long flags,
void *data) void *data)
{ {
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL, *dev_buffer = NULL; char *buffer = NULL, *dev_buffer = NULL;
const char *name = NULL, *dev_name = NULL, *info = NULL;
bool binary = true; bool binary = true;
int error; int error;
int requires_dev = 0; int requires_dev = 0;
struct path dev_path; struct path tmp_path, *dev_path = NULL;
AA_BUG(!label); AA_BUG(!label);
AA_BUG(!path); AA_BUG(!path);
dev_name = orig_dev_name;
if (type) { if (type) {
struct file_system_type *fstype; struct file_system_type *fstype;
fstype = get_fs_type(type); fstype = get_fs_type(type);
...@@ -544,73 +521,62 @@ int aa_new_mount(struct aa_label *label, const char *orig_dev_name, ...@@ -544,73 +521,62 @@ int aa_new_mount(struct aa_label *label, const char *orig_dev_name,
if (!dev_name || !*dev_name) if (!dev_name || !*dev_name)
return -ENOENT; return -ENOENT;
error = kern_path(dev_name, LOOKUP_FOLLOW, &dev_path); error = kern_path(dev_name, LOOKUP_FOLLOW, &tmp_path);
if (error) if (error)
return error; return error;
dev_path = &tmp_path;
} }
} }
get_buffers(buffer, dev_buffer); get_buffers(buffer, dev_buffer);
if (type && requires_dev) { if (dev_path) {
error = aa_path_name(&dev_path,
path_flags(labels_profile(label),
&dev_path),
dev_buffer, &dev_name, &info,
labels_profile(label)->disconnected);
path_put(&dev_path);
if (error)
goto error;
}
error = aa_path_name(path, path_flags(labels_profile(label), path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error)
goto error;
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
match_mnt(profile, name, dev_name, type, flags, data, match_mnt(profile, path, buffer, dev_path, dev_buffer,
binary)); type, flags, data, binary));
} else {
cleanup: error = fn_for_each_confined(label, profile,
match_mnt_path_str(profile, path, buffer, dev_name,
type, flags, data, binary, NULL));
}
put_buffers(buffer, dev_buffer); put_buffers(buffer, dev_buffer);
if (dev_path)
path_put(dev_path);
return error; return error;
error:
error = fn_for_each(label, profile,
audit_mount(labels_profile(label), OP_MOUNT, name,
dev_name, type, NULL, flags, data,
AA_MAY_MOUNT, &nullperms, info, error));
goto cleanup;
} }
static int profile_umount(struct aa_profile *profile, const char *name) static int profile_umount(struct aa_profile *profile, struct path *path,
char *buffer)
{ {
struct aa_perms perms = { }; struct aa_perms perms = { };
const char *info = NULL; const char *name = NULL, *info = NULL;
unsigned int state; unsigned int state;
int e = 0; int error;
AA_BUG(!profile); AA_BUG(!profile);
AA_BUG(!name); AA_BUG(!path);
error = aa_path_name(path, path_flags(profile, path), buffer, &name,
&info, profile->disconnected);
if (error)
goto audit;
state = aa_dfa_match(profile->policy.dfa, state = aa_dfa_match(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT], profile->policy.start[AA_CLASS_MOUNT],
name); name);
perms = compute_mnt_perms(profile->policy.dfa, state); perms = compute_mnt_perms(profile->policy.dfa, state);
if (AA_MAY_UMOUNT & ~perms.allow) if (AA_MAY_UMOUNT & ~perms.allow)
e = -EACCES; error = -EACCES;
audit:
return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL, return audit_mount(profile, OP_UMOUNT, name, NULL, NULL, NULL, 0, NULL,
AA_MAY_UMOUNT, &perms, info, e); AA_MAY_UMOUNT, &perms, info, error);
} }
int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
{ {
struct aa_profile *profile; struct aa_profile *profile;
char *buffer = NULL; char *buffer = NULL;
const char *name, *info = NULL;
int error; int error;
struct path path = { mnt, mnt->mnt_root }; struct path path = { mnt, mnt->mnt_root };
...@@ -618,44 +584,49 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags) ...@@ -618,44 +584,49 @@ int aa_umount(struct aa_label *label, struct vfsmount *mnt, int flags)
AA_BUG(!mnt); AA_BUG(!mnt);
get_buffers(buffer); get_buffers(buffer);
error = aa_path_name(&path, path_flags(labels_profile(label), &path),
buffer, &name, &info,
labels_profile(label)->disconnected);
if (error) {
error = fn_for_each(label, profile,
audit_mount(profile, OP_UMOUNT, name, NULL,
NULL, NULL, 0, NULL, AA_MAY_UMOUNT,
&nullperms, info, error));
goto out;
}
error = fn_for_each_confined(label, profile, error = fn_for_each_confined(label, profile,
profile_umount(profile, name)); profile_umount(profile, &path, buffer));
out:
put_buffers(buffer); put_buffers(buffer);
return error; return error;
} }
static int profile_pivotroot(struct aa_profile *profile, const char *new_name, /* helper fn for transition on pivotroot
const char *old_name, struct aa_label **trans) *
* Returns: label for transition or ERR_PTR. Does not return NULL
*/
static struct aa_label *build_pivotroot(struct aa_profile *profile,
const struct path *new_path,
char *new_buffer,
const struct path *old_path,
char *old_buffer)
{ {
struct aa_label *target = NULL; const char *old_name, *new_name = NULL, *info = NULL;
const char *trans_name = NULL; const char *trans_name = NULL;
struct aa_label *target = NULL;
struct aa_perms perms = { }; struct aa_perms perms = { };
const char *info = NULL;
unsigned int state; unsigned int state;
int error = -EACCES; int error;
AA_BUG(!profile); AA_BUG(!profile);
AA_BUG(!new_name); AA_BUG(!new_path);
AA_BUG(!old_name); AA_BUG(!old_path);
AA_BUG(!trans);
/* TODO: actual domain transition computation for multiple if (profile_unconfined(profile))
* profiles return aa_get_newest_label(&profile->label);
*/
error = aa_path_name(old_path, path_flags(profile, old_path),
old_buffer, &old_name, &info,
profile->disconnected);
if (error)
goto audit;
error = aa_path_name(new_path, path_flags(profile, new_path),
new_buffer, &new_name, &info,
profile->disconnected);
if (error)
goto audit;
error = -EACCES;
state = aa_dfa_match(profile->policy.dfa, state = aa_dfa_match(profile->policy.dfa,
profile->policy.start[AA_CLASS_MOUNT], profile->policy.start[AA_CLASS_MOUNT],
new_name); new_name);
...@@ -664,33 +635,34 @@ static int profile_pivotroot(struct aa_profile *profile, const char *new_name, ...@@ -664,33 +635,34 @@ static int profile_pivotroot(struct aa_profile *profile, const char *new_name,
perms = compute_mnt_perms(profile->policy.dfa, state); perms = compute_mnt_perms(profile->policy.dfa, state);
if (AA_MAY_PIVOTROOT & perms.allow) { if (AA_MAY_PIVOTROOT & perms.allow) {
error = 0;
if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { if ((perms.xindex & AA_X_TYPE_MASK) == AA_X_TABLE) {
target = x_table_lookup(profile, perms.xindex, target = x_table_lookup(profile, perms.xindex,
&trans_name); &trans_name);
if (!target) if (!target)
error = -ENOENT; error = -ENOENT;
else }
*trans = target;
} else
error = 0;
} }
audit:
error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name, error = audit_mount(profile, OP_PIVOTROOT, new_name, old_name,
NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT, NULL, trans_name, 0, NULL, AA_MAY_PIVOTROOT,
&perms, info, error); &perms, info, error);
if (!*trans) if (error) {
aa_put_label(target); aa_put_label(target);
return ERR_PTR(error);
} else if (target)
return target;
return error; return aa_get_newest_label(&profile->label);
} }
int aa_pivotroot(struct aa_label *label, struct path *old_path, int aa_pivotroot(struct aa_label *label, const struct path *old_path,
struct path *new_path) const struct path *new_path)
{ {
struct aa_profile *profile; struct aa_profile *profile;
struct aa_label *target = NULL; struct aa_label *target = NULL;
char *old_buffer = NULL, *new_buffer = NULL; char *old_buffer = NULL, *new_buffer = NULL, *info = NULL;
const char *old_name, *new_name = NULL, *info = NULL;
int error; int error;
AA_BUG(!label); AA_BUG(!label);
...@@ -698,32 +670,33 @@ int aa_pivotroot(struct aa_label *label, struct path *old_path, ...@@ -698,32 +670,33 @@ int aa_pivotroot(struct aa_label *label, struct path *old_path,
AA_BUG(!new_path); AA_BUG(!new_path);
get_buffers(old_buffer, new_buffer); get_buffers(old_buffer, new_buffer);
error = aa_path_name(old_path, path_flags(labels_profile(label), target = fn_label_build(label, profile, GFP_ATOMIC,
old_path), build_pivotroot(profile, new_path, new_buffer,
old_buffer, &old_name, &info, old_path, old_buffer));
labels_profile(label)->disconnected); if (!target) {
if (error) info = "label build failed";
goto error; error = -ENOMEM;
error = aa_path_name(new_path, path_flags(labels_profile(label), goto fail;
new_path), } else if (!IS_ERR(target)) {
new_buffer, &new_name, &info, error = aa_replace_current_label(target);
labels_profile(label)->disconnected); if (error) {
if (error) /* TODO: audit target */
goto error; aa_put_label(target);
error = fn_for_each(label, profile, goto out;
profile_pivotroot(profile, new_name, old_name, }
&target)); } else
/* already audited error */
error = PTR_ERR(target);
out: out:
put_buffers(old_buffer, new_buffer); put_buffers(old_buffer, new_buffer);
if (target)
error = aa_replace_current_label(target);
return error; return error;
error: fail:
/* TODO: add back in auditing of new_name and old_name */
error = fn_for_each(label, profile, error = fn_for_each(label, profile,
audit_mount(profile, OP_PIVOTROOT, new_name, old_name, audit_mount(profile, OP_PIVOTROOT, NULL /*new_name */,
NULL /* old_name */,
NULL, NULL, NULL, NULL,
0, NULL, AA_MAY_PIVOTROOT, &nullperms, info, 0, NULL, AA_MAY_PIVOTROOT, &nullperms, info,
error)); error));
......
...@@ -49,8 +49,8 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen) ...@@ -49,8 +49,8 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
* of chroot) and specifically directed to connect paths to * of chroot) and specifically directed to connect paths to
* namespace root. * namespace root.
*/ */
static int disconnect(struct path *path, char *buf, char **name, int flags, static int disconnect(const struct path *path, char *buf, char **name,
const char *disconnected) int flags, const char *disconnected)
{ {
int error = 0; int error = 0;
...@@ -89,7 +89,7 @@ static int disconnect(struct path *path, char *buf, char **name, int flags, ...@@ -89,7 +89,7 @@ static int disconnect(struct path *path, char *buf, char **name, int flags,
* When no error the path name is returned in @name which points to * When no error the path name is returned in @name which points to
* to a position in @buf * to a position in @buf
*/ */
static int d_namespace_path(struct path *path, char *buf, char **name, static int d_namespace_path(const struct path *path, char *buf, char **name,
int flags, const char *disconnected) int flags, const char *disconnected)
{ {
char *res; char *res;
...@@ -162,7 +162,7 @@ static int d_namespace_path(struct path *path, char *buf, char **name, ...@@ -162,7 +162,7 @@ static int d_namespace_path(struct path *path, char *buf, char **name,
* allocated. * allocated.
*/ */
if (d_unlinked(path->dentry) && d_is_positive(path->dentry) && if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
!(flags & PATH_MEDIATE_DELETED)) { !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
error = -ENOENT; error = -ENOENT;
goto out; goto out;
} }
...@@ -198,8 +198,8 @@ static int d_namespace_path(struct path *path, char *buf, char **name, ...@@ -198,8 +198,8 @@ static int d_namespace_path(struct path *path, char *buf, char **name,
* *
* Returns: %0 else error code if could retrieve name * Returns: %0 else error code if could retrieve name
*/ */
int aa_path_name(struct path *path, int flags, char *buffer, const char **name, int aa_path_name(const struct path *path, int flags, char *buffer,
const char **info, const char *disconnected) const char **name, const char **info, const char *disconnected)
{ {
char *str = NULL; char *str = NULL;
int error = d_namespace_path(path, buffer, &str, flags, disconnected); int error = d_namespace_path(path, buffer, &str, flags, disconnected);
......
...@@ -87,10 +87,11 @@ ...@@ -87,10 +87,11 @@
#include "include/match.h" #include "include/match.h"
#include "include/path.h" #include "include/path.h"
#include "include/policy.h" #include "include/policy.h"
#include "include/policy_ns.h"
#include "include/policy_unpack.h" #include "include/policy_unpack.h"
#include "include/resource.h" #include "include/resource.h"
int unprivileged_userns_apparmor_policy = 0; int unprivileged_userns_apparmor_policy = 1;
/* Note: mode names must be unique in the first character because of /* Note: mode names must be unique in the first character because of
* modechrs used to print modes on compound labels on some interfaces * modechrs used to print modes on compound labels on some interfaces
...@@ -102,6 +103,19 @@ const char *const aa_profile_mode_names[] = { ...@@ -102,6 +103,19 @@ const char *const aa_profile_mode_names[] = {
"unconfined", "unconfined",
}; };
/**
* 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);
}
/** /**
* __add_profile - add a profiles to list and label tree * __add_profile - add a profiles to list and label tree
...@@ -199,6 +213,8 @@ void __aa_profile_list_release(struct list_head *head) ...@@ -199,6 +213,8 @@ void __aa_profile_list_release(struct list_head *head)
*/ */
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)
...@@ -220,7 +236,16 @@ void aa_free_profile(struct aa_profile *profile) ...@@ -220,7 +236,16 @@ void aa_free_profile(struct aa_profile *profile)
aa_put_dfa(profile->xmatch); aa_put_dfa(profile->xmatch);
aa_put_dfa(profile->policy.dfa); aa_put_dfa(profile->policy.dfa);
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);
kzfree(profile); kzfree(profile);
} }
...@@ -617,43 +642,43 @@ static int audit_policy(struct aa_label *label, const char *op, ...@@ -617,43 +642,43 @@ static int audit_policy(struct aa_label *label, const char *op,
return error; return error;
} }
bool policy_view_capable(void) /**
* policy_view_capable - check if viewing policy in at @ns is allowed
* ns: namespace being viewed by current task (may be NULL)
* Returns: true if viewing policy is allowed
*
* If @ns is NULL then the namespace being viewed is assumed to be the
* tasks current namespace.
*/
bool policy_view_capable(struct aa_ns *ns)
{ {
struct user_namespace *user_ns = current_user_ns(); struct user_namespace *user_ns = current_user_ns();
struct aa_ns *ns = aa_get_current_ns(); struct aa_ns *view_ns = aa_get_current_ns();
bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) ||
in_egroup_p(make_kgid(user_ns, 0));
bool response = false; bool response = false;
if (!ns)
ns = view_ns;
if (ns_capable(user_ns, CAP_MAC_ADMIN) && if (root_in_user_ns && aa_ns_visible(view_ns, ns, true) &&
(user_ns == &init_user_ns || (user_ns == &init_user_ns ||
(unprivileged_userns_apparmor_policy != 0 && (unprivileged_userns_apparmor_policy != 0 &&
user_ns->level == 1 && ns != root_ns))) user_ns->level == view_ns->level)))
response = true; response = true;
aa_put_ns(ns); aa_put_ns(view_ns);
return response; return response;
} }
bool policy_admin_capable(void) bool policy_admin_capable(struct aa_ns *ns)
{
return policy_view_capable() && !aa_g_lock_policy;
}
bool aa_may_open_profiles(void)
{ {
struct user_namespace *user_ns = current_user_ns(); struct user_namespace *user_ns = current_user_ns();
struct aa_ns *ns = aa_get_current_ns(); bool capable = ns_capable(user_ns, CAP_MAC_ADMIN);
bool root_in_user_ns = uid_eq(current_euid(), make_kuid(user_ns, 0)) ||
in_egroup_p(make_kgid(user_ns, 0));
bool response = false;
if (root_in_user_ns && AA_DEBUG("cap_mac_admin? %d\n", capable);
(user_ns == &init_user_ns || AA_DEBUG("policy locked? %d\n", aa_g_lock_policy);
(unprivileged_userns_apparmor_policy != 0 &&
user_ns->level == 1 && ns != root_ns)))
response = true;
aa_put_ns(ns);
return response; return policy_view_capable(ns) && capable && !aa_g_lock_policy;
} }
/** /**
...@@ -663,7 +688,7 @@ bool aa_may_open_profiles(void) ...@@ -663,7 +688,7 @@ bool aa_may_open_profiles(void)
* *
* Returns: 0 if the task is allowed to manipulate policy else error * Returns: 0 if the task is allowed to manipulate policy else error
*/ */
int aa_may_manage_policy(struct aa_label *label, u32 mask) int aa_may_manage_policy(struct aa_label *label, struct aa_ns *ns, u32 mask)
{ {
const char *op; const char *op;
...@@ -679,7 +704,7 @@ int aa_may_manage_policy(struct aa_label *label, u32 mask) ...@@ -679,7 +704,7 @@ int aa_may_manage_policy(struct aa_label *label, u32 mask)
return audit_policy(label, op, NULL, NULL, "policy_locked", return audit_policy(label, op, NULL, NULL, "policy_locked",
-EACCES); -EACCES);
if (!policy_admin_capable()) if (!policy_admin_capable(ns))
return audit_policy(label, op, NULL, NULL, "not policy admin", return audit_policy(label, op, NULL, NULL, "not policy admin",
-EACCES); -EACCES);
...@@ -825,10 +850,10 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) ...@@ -825,10 +850,10 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
/** /**
* aa_replace_profiles - replace profile(s) on the profile list * aa_replace_profiles - replace profile(s) on the profile list
* @view: namespace load is viewed from
* @label: label that is attempting to load/replace policy * @label: label that is attempting to load/replace policy
* @mask: permission mask * @mask: permission mask
* @udata: serialized data stream (NOT NULL) * @udata: serialized data stream (NOT NULL)
* @size: size of the serialized data stream
* *
* unpack and replace a profile on the profile list and uses of that profile * unpack and replace a profile on the profile list and uses of that profile
* by any aa_task_ctx. If the profile does not exist on the profile list * by any aa_task_ctx. If the profile does not exist on the profile list
...@@ -836,10 +861,10 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new) ...@@ -836,10 +861,10 @@ static struct aa_profile *update_to_newest_parent(struct aa_profile *new)
* *
* Returns: size of data consumed else error code on failure. * Returns: size of data consumed else error code on failure.
*/ */
ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ssize_t aa_replace_profiles(struct aa_ns *view, struct aa_label *label,
size_t size) u32 mask, struct aa_loaddata *udata)
{ {
const char *ns_name, *name = NULL, *info = NULL; const char *ns_name, *info = NULL;
struct aa_ns *ns = NULL; struct aa_ns *ns = NULL;
struct aa_load_ent *ent, *tmp; struct aa_load_ent *ent, *tmp;
const char *op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD; const char *op = mask & AA_MAY_REPLACE_POLICY ? OP_PROF_REPL : OP_PROF_LOAD;
...@@ -848,7 +873,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ...@@ -848,7 +873,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
LIST_HEAD(lh); LIST_HEAD(lh);
/* released below */ /* released below */
error = aa_unpack(udata, size, &lh, &ns_name); error = aa_unpack(udata, &lh, &ns_name);
if (error) if (error)
goto out; goto out;
...@@ -877,21 +902,22 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ...@@ -877,21 +902,22 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
count++; count++;
} }
if (ns_name) { if (ns_name) {
ns = aa_prepare_ns(labels_ns(label), ns_name); ns = aa_prepare_ns(view, ns_name);
if (!ns) { if (IS_ERR(ns)) {
info = "failed to prepare namespace"; info = "failed to prepare namespace";
error = -ENOMEM; error = PTR_ERR(ns);
ns = NULL;
goto fail; goto fail;
} }
} else } else
ns = aa_get_ns(labels_ns(label)); ns = aa_get_ns(view);
mutex_lock(&ns->lock); mutex_lock(&ns->lock);
/* setup parent and ns info */ /* setup parent and ns info */
list_for_each_entry(ent, &lh, list) { list_for_each_entry(ent, &lh, list) {
struct aa_policy *policy; struct aa_policy *policy;
name = ent->new->base.hname; ent->new->rawdata = aa_get_loaddata(udata);
error = __lookup_replace(ns, ent->new->base.hname, error = __lookup_replace(ns, ent->new->base.hname,
!(mask & AA_MAY_REPLACE_POLICY), !(mask & AA_MAY_REPLACE_POLICY),
&ent->old, &info); &ent->old, &info);
...@@ -920,7 +946,6 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ...@@ -920,7 +946,6 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
if (!p) { if (!p) {
error = -ENOENT; error = -ENOENT;
info = "parent does not exist"; info = "parent does not exist";
name = ent->new->base.hname;
goto fail_lock; goto fail_lock;
} }
rcu_assign_pointer(ent->new->parent, aa_get_profile(p)); rcu_assign_pointer(ent->new->parent, aa_get_profile(p));
...@@ -985,13 +1010,26 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ...@@ -985,13 +1010,26 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
if (error) if (error)
return error; return error;
return size; return udata->size;
fail_lock: fail_lock:
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
fail:
error = audit_policy(label, op, ns_name, name, info, error);
/* audit cause of failure */
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
fail:
audit_policy(label, op, ns_name, ent->new->base.hname, info, error);
/* audit status that rest of profiles in the atomic set failed too */
info = "valid profile in failed atomic policy load";
list_for_each_entry(tmp, &lh, list) {
if (tmp == ent) {
info = "unchecked profile in failed atomic policy load";
/* skip entry that caused failure */
continue;
}
op = (!ent->old) ? OP_PROF_LOAD : OP_PROF_REPL;
audit_policy(label, op, ns_name, tmp->new->base.hname, info, error);
}
list_for_each_entry_safe(ent, tmp, &lh, list) { list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list); list_del_init(&ent->list);
aa_load_ent_free(ent); aa_load_ent_free(ent);
...@@ -1002,6 +1040,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ...@@ -1002,6 +1040,7 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
/** /**
* aa_remove_profiles - remove profile(s) from the system * aa_remove_profiles - remove profile(s) from the system
* @view: namespace the remove is being done from
* @label: label attempting to remove policy * @label: label attempting to remove policy
* @fqname: name of the profile or namespace to remove (NOT NULL) * @fqname: name of the profile or namespace to remove (NOT NULL)
* @size: size of the name * @size: size of the name
...@@ -1013,7 +1052,8 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata, ...@@ -1013,7 +1052,8 @@ ssize_t aa_replace_profiles(struct aa_label *label, u32 mask, void *udata,
* *
* Returns: size of data consume else error code if fails * Returns: size of data consume else error code if fails
*/ */
ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size) ssize_t aa_remove_profiles(struct aa_ns *view, struct aa_label *label,
char *fqname, size_t size)
{ {
struct aa_ns *root = NULL, *ns = NULL; struct aa_ns *root = NULL, *ns = NULL;
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
...@@ -1027,7 +1067,7 @@ ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size) ...@@ -1027,7 +1067,7 @@ ssize_t aa_remove_profiles(struct aa_label *label, char *fqname, size_t size)
goto fail; goto fail;
} }
root = labels_ns(label); root = view;
if (fqname[0] == ':') { if (fqname[0] == ':') {
name = aa_split_fqname(fqname, &ns_name); name = aa_split_fqname(fqname, &ns_name);
......
...@@ -147,21 +147,6 @@ void aa_free_ns(struct aa_ns *ns) ...@@ -147,21 +147,6 @@ void aa_free_ns(struct aa_ns *ns)
kzfree(ns); kzfree(ns);
} }
/**
* __aa_findn_ns - find a namespace on a list by @name
* @head: list to search for namespace on (NOT NULL)
* @name: name of namespace to look for (NOT NULL)
* @n: length of @name
* Returns: unrefcounted namespace
*
* Requires: rcu_read_lock be held
*/
static struct aa_ns *__aa_findn_ns(struct list_head *head, const char *name,
size_t n)
{
return (struct aa_ns *)__policy_strn_find(head, name, n);
}
/** /**
* aa_find_ns - look up a profile namespace on the namespace list * aa_find_ns - look up a profile namespace on the namespace list
* @root: namespace to search in (NOT NULL) * @root: namespace to search in (NOT NULL)
...@@ -199,43 +184,84 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name) ...@@ -199,43 +184,84 @@ struct aa_ns *aa_find_ns(struct aa_ns *root, const char *name)
return aa_findn_ns(root, name, strlen(name)); return aa_findn_ns(root, name, strlen(name));
} }
/** static struct aa_ns *__aa_create_ns(struct aa_ns *parent, const char *name,
* aa_prepare_ns - find an existing or create a new namespace of @name struct dentry *dir)
* @root: ns to treat as root
* @name: the namespace to find or add (NOT NULL)
*
* Returns: refcounted namespace or NULL if failed to create one
*/
struct aa_ns *aa_prepare_ns(struct aa_ns *root, const char *name)
{ {
struct aa_ns *ns; struct aa_ns *ns;
int error;
mutex_lock(&root->lock); AA_BUG(!parent);
/* try and find the specified ns and if it doesn't exist create it */ AA_BUG(!name);
/* released by caller */ AA_BUG(!mutex_is_locked(&parent->lock));
ns = aa_get_ns(__aa_findn_ns(&root->sub_ns, name, strlen(name)));
if (!ns) { ns = alloc_ns(parent->base.hname, name);
ns = alloc_ns(root->base.hname, name);
if (!ns) if (!ns)
goto out; return NULL;
mutex_lock(&ns->lock); mutex_lock(&ns->lock);
if (__aa_fs_ns_mkdir(ns, ns_subns_dir(root), name)) { error = __aa_fs_ns_mkdir(ns, ns_subns_dir(parent), name, dir);
if (error) {
AA_ERROR("Failed to create interface for ns %s\n", AA_ERROR("Failed to create interface for ns %s\n",
ns->base.name); ns->base.name);
mutex_unlock(&ns->lock); mutex_unlock(&ns->lock);
aa_free_ns(ns); aa_free_ns(ns);
ns = NULL; return ERR_PTR(error);
goto out; } else {
} ns->parent = aa_get_ns(parent);
ns->parent = aa_get_ns(root); ns->level = parent->level + 1;
ns->level = root->level + 1; list_add_rcu(&ns->base.list, &parent->sub_ns);
list_add_rcu(&ns->base.list, &root->sub_ns);
/* add list ref */ /* add list ref */
aa_get_ns(ns); aa_get_ns(ns);
mutex_unlock(&ns->lock);
} }
out: mutex_unlock(&ns->lock);
mutex_unlock(&root->lock);
return ns;
}
/**
* aa_create_ns - create an ns, fail if it already exists
* @parent: the parent of the namespace being created
* @name: the name of the namespace
* @dir: if not null the dir to put the ns entries in
*
* Returns: the a refcounted ns that has been add or an ERR_PTR
*/
struct aa_ns *aa_create_ns(struct aa_ns *parent, const char *name,
struct dentry *dir)
{
struct aa_ns *ns;
mutex_lock(&parent->lock);
/* try and find the specified ns */
/* released by caller */
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name));
if (!ns)
ns = __aa_create_ns(parent, name, dir);
else
ns = ERR_PTR(-EEXIST);
mutex_unlock(&parent->lock);
/* return ref */
return ns;
}
/**
* aa_prepare_ns - find an existing or create a new namespace of @name
* @parent: ns to treat as parent
* @name: the namespace to find or add (NOT NULL)
*
* Returns: refcounted namespace or PTR_ERR if failed to create one
*/
struct aa_ns *aa_prepare_ns(struct aa_ns *parent, const char *name)
{
struct aa_ns *ns;
mutex_lock(&parent->lock);
/* try and find the specified ns and if it doesn't exist create it */
/* released by caller */
ns = aa_get_ns(__aa_find_ns(&parent->sub_ns, name));
if (!ns)
ns = __aa_create_ns(parent, name, NULL);
mutex_unlock(&parent->lock);
/* return ref */ /* return ref */
return ns; return ns;
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include <linux/ctype.h> #include <linux/ctype.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/jhash.h>
#include "include/apparmor.h" #include "include/apparmor.h"
#include "include/audit.h" #include "include/audit.h"
...@@ -124,6 +125,15 @@ static int audit_iface(struct aa_profile *new, const char *ns_name, ...@@ -124,6 +125,15 @@ static int audit_iface(struct aa_profile *new, const char *ns_name,
return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, 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 */ /* test if read will be in packed data bounds */
static bool inbounds(struct aa_ext *e, size_t size) static bool inbounds(struct aa_ext *e, size_t size)
{ {
...@@ -495,6 +505,30 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile) ...@@ -495,6 +505,30 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
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)
...@@ -507,6 +541,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) ...@@ -507,6 +541,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
const char *tmpname, *tmpns = NULL, *name = NULL; const char *tmpname, *tmpns = NULL, *name = NULL;
const char *info = "failed to unpack profile"; const char *info = "failed to unpack profile";
size_t size = 0, ns_len; size_t size = 0, 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;
...@@ -697,6 +734,45 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) ...@@ -697,6 +734,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, &params))
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;
...@@ -827,7 +903,6 @@ struct aa_load_ent *aa_load_ent_alloc(void) ...@@ -827,7 +903,6 @@ struct aa_load_ent *aa_load_ent_alloc(void)
/** /**
* aa_unpack - unpack packed binary profile(s) data loaded from user space * aa_unpack - unpack packed binary profile(s) data loaded from user space
* @udata: user data copied to kmem (NOT NULL) * @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 * @lh: list to place unpacked profiles in a aa_repl_ws
* @ns: Returns namespace profile is in if specified else NULL (NOT NULL) * @ns: Returns namespace profile is in if specified else NULL (NOT NULL)
* *
...@@ -837,15 +912,15 @@ struct aa_load_ent *aa_load_ent_alloc(void) ...@@ -837,15 +912,15 @@ struct aa_load_ent *aa_load_ent_alloc(void)
* *
* Returns: profile(s) on @lh else error pointer if fails to unpack * 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_load_ent *tmp, *ent;
struct aa_profile *profile = NULL; struct aa_profile *profile = NULL;
int error; int error;
struct aa_ext e = { struct aa_ext e = {
.start = udata, .start = udata->data,
.end = udata + size, .end = udata->data + udata->size,
.pos = udata, .pos = udata->data,
}; };
*ns = NULL; *ns = NULL;
...@@ -855,7 +930,6 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) ...@@ -855,7 +930,6 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
error = verify_header(&e, e.pos == e.start, ns); error = verify_header(&e, e.pos == e.start, ns);
if (error) if (error)
goto fail; goto fail;
start = e.pos; start = e.pos;
profile = unpack_profile(&e, &ns_name); profile = unpack_profile(&e, &ns_name);
if (IS_ERR(profile)) { if (IS_ERR(profile)) {
...@@ -883,7 +957,15 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns) ...@@ -883,7 +957,15 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
ent->ns_name = ns_name; ent->ns_name = ns_name;
list_add_tail(&ent->list, lh); list_add_tail(&ent->list, lh);
} }
udata->abi = e.version & K_ABI_MASK;
if (aa_g_hash_policy) {
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; return 0;
fail_profile: fail_profile:
......
...@@ -46,8 +46,42 @@ static struct file_system_type fs_type = { ...@@ -46,8 +46,42 @@ static struct file_system_type fs_type = {
.kill_sb = kill_litter_super, .kill_sb = kill_litter_super,
}; };
int securityfs_pin_fs(void)
{
return simple_pin_fs(&fs_type, &mount, &mount_count);
}
int __securityfs_setup_d_inode(struct inode *dir, struct dentry *dentry,
umode_t mode, void *data,
const struct file_operations *fops,
const struct inode_operations *iops)
{
bool is_dir = S_ISDIR(mode);
struct inode *inode = new_inode(dir->i_sb);
if (!inode)
return -ENOMEM;
inode->i_ino = get_next_ino();
inode->i_mode = mode;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
inode->i_private = data;
if (is_dir) {
inode->i_op = iops ? iops : &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inc_nlink(inode);
inc_nlink(dir);
} else {
inode->i_fop = fops;
}
d_instantiate(dentry, inode);
dget(dentry);
return 0;
}
EXPORT_SYMBOL_GPL(__securityfs_setup_d_inode);
/** /**
* securityfs_create_file - create a file in the securityfs filesystem * securityfs_create_dentry - create a file/dir in the securityfs filesystem
* *
* @name: a pointer to a string containing the name of the file to create. * @name: a pointer to a string containing the name of the file to create.
* @mode: the permission that the file should have * @mode: the permission that the file should have
...@@ -59,8 +93,10 @@ static struct file_system_type fs_type = { ...@@ -59,8 +93,10 @@ static struct file_system_type fs_type = {
* the open() call. * the open() call.
* @fops: a pointer to a struct file_operations that should be used for * @fops: a pointer to a struct file_operations that should be used for
* this file. * this file.
* @iops: a point to a struct of inode_operations that should be used for
* this file/dir
* *
* This is the basic "create a file" function for securityfs. It allows for a * This is the basic "create a xxx" function for securityfs. It allows for a
* wide range of flexibility in creating a file, or a directory (if you * wide range of flexibility in creating a file, or a directory (if you
* want to create a directory, the securityfs_create_dir() function is * want to create a directory, the securityfs_create_dir() function is
* recommended to be used instead). * recommended to be used instead).
...@@ -74,13 +110,14 @@ static struct file_system_type fs_type = { ...@@ -74,13 +110,14 @@ static struct file_system_type fs_type = {
* If securityfs is not enabled in the kernel, the value %-ENODEV is * If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned. * returned.
*/ */
struct dentry *securityfs_create_file(const char *name, umode_t mode, struct dentry *securityfs_create_dentry(const char *name, umode_t mode,
struct dentry *parent, void *data, struct dentry *parent, void *data,
const struct file_operations *fops) const struct file_operations *fops,
const struct inode_operations *iops)
{ {
struct dentry *dentry; struct dentry *dentry;
int is_dir = S_ISDIR(mode); int is_dir = S_ISDIR(mode);
struct inode *dir, *inode; struct inode *dir;
int error; int error;
if (!is_dir) { if (!is_dir) {
...@@ -109,26 +146,9 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode, ...@@ -109,26 +146,9 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode,
goto out1; goto out1;
} }
inode = new_inode(dir->i_sb); error = __securityfs_setup_d_inode(dir, dentry, mode, data, fops, iops);
if (!inode) { if (error)
error = -ENOMEM;
goto out1; goto out1;
}
inode->i_ino = get_next_ino();
inode->i_mode = mode;
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
inode->i_private = data;
if (is_dir) {
inode->i_op = &simple_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
inc_nlink(inode);
inc_nlink(dir);
} else {
inode->i_fop = fops;
}
d_instantiate(dentry, inode);
dget(dentry);
mutex_unlock(&dir->i_mutex); mutex_unlock(&dir->i_mutex);
return dentry; return dentry;
...@@ -140,6 +160,39 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode, ...@@ -140,6 +160,39 @@ struct dentry *securityfs_create_file(const char *name, umode_t mode,
simple_release_fs(&mount, &mount_count); simple_release_fs(&mount, &mount_count);
return dentry; return dentry;
} }
EXPORT_SYMBOL_GPL(securityfs_create_dentry);
/**
* securityfs_create_file - create a file in the securityfs filesystem
*
* @name: a pointer to a string containing the name of the file to create.
* @mode: the permission that the file should have
* @parent: a pointer to the parent dentry for this file. This should be a
* directory dentry if set. If this parameter is %NULL, then the
* file will be created in the root of the securityfs filesystem.
* @data: a pointer to something that the caller will want to get to later
* on. The inode.i_private pointer will point to this value on
* the open() call.
* @fops: a pointer to a struct file_operations that should be used for
* this file.
*
* This function creates a file in securityfs with the given @name.
*
* This function returns a pointer to a dentry if it succeeds. This
* pointer must be passed to the securityfs_remove() function when the file is
* to be removed (no automatic cleanup happens if your module is unloaded,
* you are responsible here). If an error occurs, the function will return
* the error value (via ERR_PTR).
*
* If securityfs is not enabled in the kernel, the value %-ENODEV is
* returned.
*/
struct dentry *securityfs_create_file(const char *name, umode_t mode,
struct dentry *parent, void *data,
const struct file_operations *fops)
{
return securityfs_create_dentry(name, mode, parent, data, fops, NULL);
}
EXPORT_SYMBOL_GPL(securityfs_create_file); EXPORT_SYMBOL_GPL(securityfs_create_file);
/** /**
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment