UBUNTU: SAUCE: apparmor4.0.0 [52/90]: enable userspace upcall for mediation
BugLink: http://bugs.launchpad.net/bugs/2028253 There are cases where userspace can provide additional information that may be needed to make the correct mediation decision. Signed-off-by: John Johansen <john.johansen@canonical.com> (cherry picked from https://gitlab.com/jjohansen/apparmor-kernel) Signed-off-by: Andrea Righi <andrea.righi@canonical.com> (cherry picked from commit c0cb33a681e82df1fda919edfd49fb7cf09615f3 https://git.launchpad.net/~apparmor-dev/ubuntu-kernel-next) Signed-off-by: Paolo Pisati <paolo.pisati@canonical.com>
This commit is contained in:
committed by
Paolo Pisati
parent
f3973fd0c5
commit
00f709701f
@@ -0,0 +1,106 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
#ifndef _UAPI_LINUX_APPARMOR_H
|
||||
#define _UAPI_LINUX_APPARMOR_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define APPARMOR_MODESET_AUDIT 1
|
||||
#define APPARMOR_MODESET_ALLOWED 2
|
||||
#define APPARMOR_MODESET_ENFORCE 4
|
||||
#define APPARMOR_MODESET_HINT 8
|
||||
#define APPARMOR_MODESET_STATUS 16
|
||||
#define APPARMOR_MODESET_ERROR 32
|
||||
#define APPARMOR_MODESET_KILL 64
|
||||
#define APPARMOR_MODESET_USER 128
|
||||
|
||||
enum apparmor_notif_type {
|
||||
APPARMOR_NOTIF_RESP,
|
||||
APPARMOR_NOTIF_CANCEL,
|
||||
APPARMOR_NOTIF_INTERUPT,
|
||||
APPARMOR_NOTIF_ALIVE,
|
||||
APPARMOR_NOTIF_OP
|
||||
};
|
||||
|
||||
#define APPARMOR_NOTIFY_VERSION 3
|
||||
|
||||
/* base notification struct embedded as head of notifications to userspace */
|
||||
struct apparmor_notif_common {
|
||||
__u16 len; /* actual len data */
|
||||
__u16 version; /* interface version */
|
||||
} __attribute__((packed));
|
||||
|
||||
struct apparmor_notif_filter {
|
||||
struct apparmor_notif_common base;
|
||||
__u32 modeset; /* which notification mode */
|
||||
__u32 ns; /* offset into data */
|
||||
__u32 filter; /* offset into data */
|
||||
|
||||
__u8 data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct apparmor_notif {
|
||||
struct apparmor_notif_common base;
|
||||
__u16 ntype; /* notify type */
|
||||
__u8 signalled;
|
||||
__u8 reserved;
|
||||
__u64 id; /* unique id, not gloablly unique*/
|
||||
__s32 error; /* error if unchanged */
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct apparmor_notif_update {
|
||||
struct apparmor_notif base;
|
||||
__u16 ttl; /* max keep alives left */
|
||||
} __attribute__((packed));
|
||||
|
||||
/* userspace response to notification that expects a response */
|
||||
struct apparmor_notif_resp {
|
||||
struct apparmor_notif base;
|
||||
__s32 error; /* error if unchanged */
|
||||
__u32 allow;
|
||||
__u32 deny;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct apparmor_notif_op {
|
||||
struct apparmor_notif base;
|
||||
__u32 allow;
|
||||
__u32 deny;
|
||||
pid_t pid; /* pid of task causing notification */
|
||||
__u32 label; /* offset into data */
|
||||
__u16 class;
|
||||
__u16 op;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct apparmor_notif_file {
|
||||
struct apparmor_notif_op base;
|
||||
uid_t suid, ouid;
|
||||
__u32 name; /* offset into data */
|
||||
|
||||
__u8 data[];
|
||||
} __attribute__((packed));
|
||||
|
||||
union apparmor_notif_all {
|
||||
struct apparmor_notif_common common;
|
||||
struct apparmor_notif_filter filter;
|
||||
struct apparmor_notif base;
|
||||
struct apparmor_notif_op op;
|
||||
struct apparmor_notif_file file;
|
||||
};
|
||||
|
||||
#define APPARMOR_IOC_MAGIC 0xF8
|
||||
|
||||
/* Flags for apparmor notification fd ioctl. */
|
||||
|
||||
#define APPARMOR_NOTIF_SET_FILTER _IOW(APPARMOR_IOC_MAGIC, 0, \
|
||||
struct apparmor_notif_filter *)
|
||||
#define APPARMOR_NOTIF_GET_FILTER _IOR(APPARMOR_IOC_MAGIC, 1, \
|
||||
struct apparmor_notif_filter *)
|
||||
#define APPARMOR_NOTIF_IS_ID_VALID _IOR(APPARMOR_IOC_MAGIC, 3, \
|
||||
__u64)
|
||||
/* RECV/SEND from userspace pov */
|
||||
#define APPARMOR_NOTIF_RECV _IOWR(APPARMOR_IOC_MAGIC, 4, \
|
||||
struct apparmor_notif *)
|
||||
#define APPARMOR_NOTIF_SEND _IOWR(APPARMOR_IOC_MAGIC, 5, \
|
||||
struct apparmor_notif_resp *)
|
||||
|
||||
#endif /* _UAPI_LINUX_APPARMOR_H */
|
||||
@@ -6,7 +6,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
|
||||
apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \
|
||||
path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
|
||||
resource.o secid.o file.o policy_ns.o label.o mount.o net.o \
|
||||
policy_compat.o af_unix.o
|
||||
policy_compat.o af_unix.o notify.o
|
||||
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
|
||||
|
||||
obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o
|
||||
|
||||
@@ -72,13 +72,13 @@ static inline int unix_fs_perm(const char *op, u32 mask,
|
||||
((flags | profile->path_flags) & PATH_MEDIATE_DELETED) ?
|
||||
__aa_path_perm(op, subj_cred, profile,
|
||||
u->addr->name->sun_path, mask,
|
||||
&cond, flags, &perms) :
|
||||
&cond, flags, &perms, false) :
|
||||
aa_audit_file(subj_cred, profile, &nullperms,
|
||||
op, mask,
|
||||
u->addr->name->sun_path, NULL,
|
||||
NULL, cond.uid,
|
||||
"Failed name lookup - deleted entry",
|
||||
-EACCES));
|
||||
-EACCES, false));
|
||||
} else {
|
||||
/* the sunpath may not be valid for this ns so use the path */
|
||||
struct path_cond cond = { u->path.dentry->d_inode->i_uid,
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <linux/zstd.h>
|
||||
#include <uapi/linux/major.h>
|
||||
#include <uapi/linux/magic.h>
|
||||
#include <uapi/linux/apparmor.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/apparmorfs.h"
|
||||
@@ -609,6 +610,171 @@ static const struct file_operations aa_fs_ns_revision_fops = {
|
||||
.release = ns_revision_release,
|
||||
};
|
||||
|
||||
|
||||
/* file hook fn for notificaions of policy actions */
|
||||
static int listener_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct aa_listener *listener = file->private_data;
|
||||
|
||||
if (listener)
|
||||
aa_put_listener(listener);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int listener_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
struct aa_listener *listener;
|
||||
|
||||
listener = aa_new_listener(NULL, GFP_KERNEL);
|
||||
if (!listener)
|
||||
return -ENOMEM;
|
||||
file->private_data = listener;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* todo: separate register and set filter */
|
||||
static long notify_set_filter(struct aa_listener *listener,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct apparmor_notif_filter *unotif;
|
||||
struct aa_ns *ns = NULL;
|
||||
long ret;
|
||||
u16 size;
|
||||
void __user *buf = (void __user *)arg;
|
||||
|
||||
if (copy_from_user(&size, buf, sizeof(size)))
|
||||
return -EFAULT;
|
||||
if (size < sizeof(unotif))
|
||||
return -EINVAL;
|
||||
/* todo upper limit on allocation size */
|
||||
unotif = kzalloc(size, GFP_KERNEL);
|
||||
if (!unotif)
|
||||
return -ENOMEM;
|
||||
|
||||
if (copy_from_user(unotif, buf, size))
|
||||
return -EFAULT;
|
||||
|
||||
ret = size;
|
||||
|
||||
/* todo validate to known modes */
|
||||
listener->mask = unotif->modeset;
|
||||
AA_DEBUG(DEBUG_UPCALL, "setting filter mask to 0x%x", listener->mask);
|
||||
if (unotif->ns)
|
||||
/* todo */
|
||||
ns = NULL;
|
||||
if (unotif->filter)
|
||||
; /* todo */
|
||||
|
||||
if (!aa_register_listener_proxy(listener, ns))
|
||||
ret = -ENOMEM;
|
||||
kfree(unotif);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static long notify_user_recv(struct aa_listener *listener,
|
||||
unsigned long arg)
|
||||
{
|
||||
u16 max_size;
|
||||
void __user *buf = (void __user *)arg;
|
||||
|
||||
if (copy_from_user(&max_size, buf, sizeof(max_size)))
|
||||
return -EFAULT;
|
||||
/* size check handled by individual message handlers */
|
||||
return aa_listener_unotif_recv(listener, buf, max_size);
|
||||
}
|
||||
|
||||
static long notify_user_response(struct aa_listener *listener,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct apparmor_notif_resp uresp = {};
|
||||
u16 size;
|
||||
void __user *buf = (void __user *)arg;
|
||||
|
||||
if (copy_from_user(&size, buf, sizeof(size)))
|
||||
return -EFAULT;
|
||||
size = min_t(size_t, size, sizeof(uresp));
|
||||
if (copy_from_user(&uresp, buf, size))
|
||||
return -EFAULT;
|
||||
|
||||
return aa_listener_unotif_response(listener, &uresp, size);
|
||||
}
|
||||
|
||||
|
||||
static long notify_is_id_valid(struct aa_listener *listener,
|
||||
unsigned long arg)
|
||||
{
|
||||
void __user *buf = (void __user *)arg;
|
||||
u64 id;
|
||||
long ret = -ENOENT;
|
||||
|
||||
if (copy_from_user(&id, buf, sizeof(id)))
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
if (__aa_find_notif(listener, id))
|
||||
ret = 0;
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long listener_ioctl(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
struct aa_listener *listener = file->private_data;
|
||||
|
||||
if (!listener)
|
||||
return -EINVAL;
|
||||
|
||||
/* todo permission to issue these commands */
|
||||
switch (cmd) {
|
||||
case APPARMOR_NOTIF_SET_FILTER:
|
||||
return notify_set_filter(listener, arg);
|
||||
case APPARMOR_NOTIF_RECV:
|
||||
return notify_user_recv(listener, arg);
|
||||
case APPARMOR_NOTIF_SEND:
|
||||
return notify_user_response(listener, arg);
|
||||
case APPARMOR_NOTIF_IS_ID_VALID:
|
||||
return notify_is_id_valid(listener, arg);
|
||||
default:
|
||||
return -EINVAL;
|
||||
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static __poll_t listener_poll(struct file *file, poll_table *pt)
|
||||
{
|
||||
struct aa_listener *listener = file->private_data;
|
||||
__poll_t mask = 0;
|
||||
|
||||
if (listener) {
|
||||
spin_lock(&listener->lock);
|
||||
poll_wait(file, &listener->wait, pt);
|
||||
if (!list_empty(&listener->notifications))
|
||||
mask |= EPOLLIN | EPOLLRDNORM;
|
||||
if (!list_empty(&listener->pending))
|
||||
mask |= EPOLLOUT | EPOLLWRNORM;
|
||||
spin_unlock(&listener->lock);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static const struct file_operations aa_sfs_notify_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = listener_open,
|
||||
.poll = listener_poll,
|
||||
// .read = notification_read,
|
||||
.llseek = generic_file_llseek,
|
||||
.release = listener_release,
|
||||
.unlocked_ioctl = listener_ioctl,
|
||||
};
|
||||
|
||||
static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
|
||||
const char *match_str, size_t match_len)
|
||||
{
|
||||
@@ -2449,6 +2615,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = {
|
||||
|
||||
static struct aa_sfs_entry aa_sfs_entry_apparmor[] = {
|
||||
AA_SFS_FILE_FOPS(".access", 0666, &aa_sfs_access),
|
||||
AA_SFS_FILE_FOPS(".notify", 0666, &aa_sfs_notify_fops),
|
||||
AA_SFS_FILE_FOPS(".stacked", 0444, &seq_ns_stacked_fops),
|
||||
AA_SFS_FILE_FOPS(".ns_stacked", 0444, &seq_ns_nsstacked_fops),
|
||||
AA_SFS_FILE_FOPS(".ns_level", 0444, &seq_ns_level_fops),
|
||||
|
||||
+19
-10
@@ -317,16 +317,11 @@ long aa_audit_data_cmp(struct apparmor_audit_data *lhs,
|
||||
{
|
||||
long res;
|
||||
|
||||
res = lhs->type - rhs->type;
|
||||
if (res)
|
||||
return res;
|
||||
/* don't compare type */
|
||||
res = lhs->class - rhs->class;
|
||||
if (res)
|
||||
return res;
|
||||
/* op uses static pointers so direct ptr comparison */
|
||||
res = lhs->op - rhs->op;
|
||||
if (res)
|
||||
return res;
|
||||
/* don't compare op */
|
||||
res = strcmp(lhs->name, rhs->name);
|
||||
if (res)
|
||||
return res;
|
||||
@@ -387,6 +382,7 @@ struct aa_audit_node *aa_dup_audit_data(struct apparmor_audit_data *orig,
|
||||
if (!copy)
|
||||
return NULL;
|
||||
|
||||
copy->knotif.ad = ©->data;
|
||||
INIT_LIST_HEAD(©->list);
|
||||
/* copy class early so aa_free_audit_node can use switch on failure */
|
||||
copy->data.class = orig->class;
|
||||
@@ -427,12 +423,12 @@ struct aa_audit_node *aa_dup_audit_data(struct apparmor_audit_data *orig,
|
||||
}
|
||||
|
||||
/* now inc counts, and copy data that can't fail */
|
||||
// don't copy error
|
||||
copy->data.error = orig->error;
|
||||
copy->data.type = orig->type;
|
||||
copy->data.request = orig->request;
|
||||
copy->data.denied = orig->denied;
|
||||
copy->data.subj_label = aa_get_label(orig->subj_label);
|
||||
copy->data.op = orig->op;
|
||||
|
||||
if (orig->subj_cred)
|
||||
copy->data.subj_cred = get_cred(orig->subj_cred);
|
||||
|
||||
@@ -470,8 +466,10 @@ fail:
|
||||
struct aa_audit_node *__node; \
|
||||
list_for_each_entry_rcu(__node, &(C)->head, list, COND) { \
|
||||
if (aa_audit_data_cmp(&__node->data, AD) == 0) \
|
||||
break; \
|
||||
goto __out_skip; \
|
||||
} \
|
||||
__node = NULL; \
|
||||
__out_skip: \
|
||||
__node; \
|
||||
})
|
||||
|
||||
@@ -515,6 +513,17 @@ struct aa_audit_node *aa_audit_cache_insert(struct aa_audit_cache *cache,
|
||||
return tmp;
|
||||
}
|
||||
|
||||
void aa_audit_cache_update_ent(struct aa_audit_cache *cache,
|
||||
struct aa_audit_node *node,
|
||||
struct apparmor_audit_data *data)
|
||||
{
|
||||
spin_lock(&cache->lock);
|
||||
node->data.denied |= data->denied;
|
||||
node->data.request = (node->data.request | data->request) &
|
||||
~node->data.denied;
|
||||
spin_unlock(&cache->lock);
|
||||
}
|
||||
|
||||
/* assumes rcu callback has already happened and list can not be walked */
|
||||
void aa_audit_cache_destroy(struct aa_audit_cache *cache)
|
||||
{
|
||||
|
||||
+13
-12
@@ -719,9 +719,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
|
||||
}
|
||||
|
||||
audit:
|
||||
aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name,
|
||||
target, new,
|
||||
cond->uid, info, error);
|
||||
aa_audit_file(subj_cred, profile, &perms, OP_EXEC, MAY_EXEC, name, target, new,
|
||||
cond->uid, info, error, false);
|
||||
if (!new || nonewprivs) {
|
||||
aa_put_label(new);
|
||||
return ERR_PTR(error);
|
||||
@@ -801,7 +800,7 @@ static int profile_onexec(const struct cred *subj_cred,
|
||||
audit:
|
||||
return aa_audit_file(subj_cred, profile, &perms, OP_EXEC,
|
||||
AA_MAY_ONEXEC, xname,
|
||||
NULL, onexec, cond->uid, info, error);
|
||||
NULL, onexec, cond->uid, info, error, false);
|
||||
}
|
||||
|
||||
/* ensure none ns domain transitions are correctly applied with onexec */
|
||||
@@ -858,7 +857,7 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred,
|
||||
OP_CHANGE_ONEXEC,
|
||||
AA_MAY_ONEXEC, bprm->filename, NULL,
|
||||
onexec, GLOBAL_ROOT_UID,
|
||||
"failed to build target label", -ENOMEM));
|
||||
"failed to build target label", -ENOMEM, false));
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
@@ -994,7 +993,8 @@ audit:
|
||||
aa_audit_file(current_cred(), profile, &nullperms,
|
||||
OP_EXEC, MAY_EXEC,
|
||||
bprm->filename, NULL, new,
|
||||
vfsuid_into_kuid(vfsuid), info, error));
|
||||
vfsuid_into_kuid(vfsuid), info, error,
|
||||
false));
|
||||
aa_put_label(new);
|
||||
goto done;
|
||||
}
|
||||
@@ -1045,7 +1045,7 @@ audit:
|
||||
AA_MAY_CHANGEHAT,
|
||||
name, hat ? hat->base.hname : NULL,
|
||||
hat ? &hat->label : NULL, GLOBAL_ROOT_UID, info,
|
||||
error);
|
||||
error, false);
|
||||
if (!hat || (error && error != -ENOENT))
|
||||
return ERR_PTR(error);
|
||||
/* if hat && error - complain mode, already audited and we adjust for
|
||||
@@ -1138,7 +1138,7 @@ fail:
|
||||
aa_audit_file(subj_cred, profile, &nullperms,
|
||||
OP_CHANGE_HAT,
|
||||
AA_MAY_CHANGEHAT, name, NULL, NULL,
|
||||
GLOBAL_ROOT_UID, info, error);
|
||||
GLOBAL_ROOT_UID, info, error, false);
|
||||
}
|
||||
}
|
||||
return ERR_PTR(error);
|
||||
@@ -1283,7 +1283,7 @@ fail:
|
||||
fn_for_each_in_ns(label, profile,
|
||||
aa_audit_file(subj_cred, profile, &perms, OP_CHANGE_HAT,
|
||||
AA_MAY_CHANGEHAT, NULL, NULL, target,
|
||||
GLOBAL_ROOT_UID, info, error));
|
||||
GLOBAL_ROOT_UID, info, error, false));
|
||||
|
||||
goto out;
|
||||
}
|
||||
@@ -1308,7 +1308,7 @@ static int change_profile_perms_wrapper(const char *op, const char *name,
|
||||
error = aa_audit_file(subj_cred, profile, perms, op, request,
|
||||
name,
|
||||
NULL, target, GLOBAL_ROOT_UID, info,
|
||||
error);
|
||||
error, false);
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -1392,7 +1392,8 @@ int aa_change_profile(const char *fqname, int flags)
|
||||
(void) fn_for_each_in_ns(label, profile,
|
||||
aa_audit_file(subj_cred, profile, &perms, op,
|
||||
request, auditname, NULL, target,
|
||||
GLOBAL_ROOT_UID, stack_msg, 0));
|
||||
GLOBAL_ROOT_UID, stack_msg, 0,
|
||||
false));
|
||||
perms.audit = 0;
|
||||
}
|
||||
|
||||
@@ -1512,7 +1513,7 @@ audit:
|
||||
aa_audit_file(subj_cred,
|
||||
profile, &perms, op, request, auditname,
|
||||
NULL, new ? new : target,
|
||||
GLOBAL_ROOT_UID, info, error));
|
||||
GLOBAL_ROOT_UID, info, error, false));
|
||||
|
||||
out:
|
||||
aa_put_label(new);
|
||||
|
||||
+134
-15
@@ -13,6 +13,7 @@
|
||||
#include <linux/file.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mount.h>
|
||||
#include <uapi/linux/apparmor.h>
|
||||
|
||||
#include "include/af_unix.h"
|
||||
#include "include/apparmor.h"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "include/ipc.h"
|
||||
#include "include/match.h"
|
||||
#include "include/net.h"
|
||||
#include "include/notify.h"
|
||||
#include "include/path.h"
|
||||
#include "include/policy.h"
|
||||
#include "include/label.h"
|
||||
@@ -77,6 +79,109 @@ static void file_audit_cb(struct audit_buffer *ab, void *va)
|
||||
}
|
||||
}
|
||||
|
||||
static int check_cache(struct aa_profile *profile,
|
||||
struct apparmor_audit_data *ad,
|
||||
struct aa_perms *perms)
|
||||
{
|
||||
struct aa_audit_node *node = NULL;
|
||||
struct aa_audit_node *hit;
|
||||
bool cache_response;
|
||||
int err;
|
||||
|
||||
AA_BUG(!profile);
|
||||
ad->subj_label = &profile->label; // normally set in aa_audit
|
||||
|
||||
/* TODO: need rcu locking around whole check once we allow
|
||||
* removing node from cache
|
||||
*/
|
||||
AA_DEBUG(DEBUG_UPCALL, "attempting prompt upcall pid %d name:'%s'",
|
||||
current->pid, ad->name);
|
||||
hit = aa_audit_cache_find(&profile->learning_cache, ad);
|
||||
if (hit) {
|
||||
AA_DEBUG(DEBUG_UPCALL, "matched node in profile cache");
|
||||
if (ad->request & hit->data.denied) {
|
||||
/* this request could only partly succeed prompting for
|
||||
* the part and failing makes no sense
|
||||
*/
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"cache hit denied, request: 0x%x by cached deny 0x%x\n",
|
||||
ad->request, hit->data.denied);
|
||||
return ad->error;
|
||||
} else if (ad->request & ~hit->data.request) {
|
||||
/* asking for more perms than is cached */
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"cache miss insufficient perms, request: 0x%x cached 0x%x\n",
|
||||
ad->request, hit->data.request);
|
||||
/* continue to do prompt */
|
||||
} else {
|
||||
AA_DEBUG(DEBUG_UPCALL, "cache hit");
|
||||
ad->error = 0;
|
||||
/* do audit */
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
AA_DEBUG(DEBUG_UPCALL, "cache miss");
|
||||
}
|
||||
/* assume we are going to dispatch */
|
||||
node = aa_dup_audit_data(ad, GFP_KERNEL);
|
||||
if (!node) {
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"notifcation failed to duplicate with error -ENOMEM\n");
|
||||
/* do audit */
|
||||
return 0;
|
||||
}
|
||||
|
||||
get_task_struct(current);
|
||||
node->data.subjtsk = current;
|
||||
node->data.type = AUDIT_APPARMOR_USER;
|
||||
node->data.request = ad->request;
|
||||
node->data.denied = ad->request & ~perms->allow;
|
||||
err = aa_do_notification(APPARMOR_NOTIF_OP, node, &cache_response);
|
||||
put_task_struct(node->data.subjtsk);
|
||||
|
||||
if (err) {
|
||||
AA_DEBUG(DEBUG_UPCALL, "notifcation failed with error %d\n",
|
||||
ad->error);
|
||||
goto return_to_audit;
|
||||
}
|
||||
|
||||
/* update based on node data for audit */
|
||||
ad->request = node->data.request;
|
||||
ad->denied = node->data.denied;
|
||||
ad->error = node->data.error;
|
||||
|
||||
if (cache_response) {
|
||||
if (hit) {
|
||||
hit:
|
||||
AA_DEBUG(DEBUG_UPCALL, "updating existing cache entry");
|
||||
aa_audit_cache_update_ent(&profile->learning_cache,
|
||||
hit, &node->data);
|
||||
} else {
|
||||
/* TODO: shouldn't add until after auditing it, or at
|
||||
* least having a refcount. Fix once removing entry is
|
||||
* allowed
|
||||
*/
|
||||
AA_DEBUG(DEBUG_UPCALL, "inserting cache entry requ 0x%x denied 0x%x",
|
||||
node->data.request, node->data.denied);
|
||||
hit = aa_audit_cache_insert(&profile->learning_cache,
|
||||
node);
|
||||
AA_DEBUG(DEBUG_UPCALL, "cache insert %s: name %s node %s\n",
|
||||
hit != node ? "lost race" : "",
|
||||
hit->data.name, node->data.name);
|
||||
if (hit != node)
|
||||
goto hit;
|
||||
AA_DEBUG(DEBUG_UPCALL, "inserted into cache");
|
||||
/* do not free node, it is now owned by the cache */
|
||||
node = NULL;
|
||||
}
|
||||
/* now to audit */
|
||||
} /* cache_response */
|
||||
|
||||
return_to_audit:
|
||||
aa_audit_node_free(node);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* aa_audit_file - handle the auditing of file operations
|
||||
* @subj_cred: cred of the subject
|
||||
@@ -97,9 +202,10 @@ int aa_audit_file(const struct cred *subj_cred,
|
||||
struct aa_profile *profile, struct aa_perms *perms,
|
||||
const char *op, u32 request, const char *name,
|
||||
const char *target, struct aa_label *tlabel,
|
||||
kuid_t ouid, const char *info, int error)
|
||||
kuid_t ouid, const char *info, int error, bool prompt)
|
||||
{
|
||||
int type = AUDIT_APPARMOR_AUTO;
|
||||
int err;
|
||||
DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op);
|
||||
|
||||
ad.subj_cred = subj_cred;
|
||||
@@ -111,6 +217,17 @@ int aa_audit_file(const struct cred *subj_cred,
|
||||
ad.info = info;
|
||||
ad.error = error;
|
||||
ad.common.u.tsk = NULL;
|
||||
ad.subjtsk = NULL;
|
||||
|
||||
if (unlikely(ad.error) && ((prompt && USER_MODE(profile)) ||
|
||||
((request & perms->prompt) &&
|
||||
((request & (perms->prompt |
|
||||
perms->allow)) == request)))) {
|
||||
err = check_cache(profile, &ad, perms);
|
||||
if (err)
|
||||
/* only happens if already cached */
|
||||
return err;
|
||||
}
|
||||
|
||||
if (likely(!ad.error)) {
|
||||
u32 mask = perms->audit;
|
||||
@@ -143,7 +260,8 @@ int aa_audit_file(const struct cred *subj_cred,
|
||||
}
|
||||
|
||||
ad.denied = ad.request & ~perms->allow;
|
||||
return aa_audit(type, profile, &ad, file_audit_cb);
|
||||
err = aa_audit(type, profile, &ad, file_audit_cb);
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +292,7 @@ static int path_name(const char *op, const struct cred *subj_cred,
|
||||
fn_for_each_confined(label, profile,
|
||||
aa_audit_file(subj_cred,
|
||||
profile, &nullperms, op, request, *name,
|
||||
NULL, NULL, cond->uid, info, error));
|
||||
NULL, NULL, cond->uid, info, error, true));
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -230,7 +348,7 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
|
||||
int __aa_path_perm(const char *op, const struct cred *subj_cred,
|
||||
struct aa_profile *profile, const char *name,
|
||||
u32 request, struct path_cond *cond, int flags,
|
||||
struct aa_perms *perms)
|
||||
struct aa_perms *perms, bool prompt)
|
||||
{
|
||||
struct aa_ruleset *rules = list_first_entry(&profile->rules,
|
||||
typeof(*rules), list);
|
||||
@@ -245,7 +363,7 @@ int __aa_path_perm(const char *op, const struct cred *subj_cred,
|
||||
e = -EACCES;
|
||||
return aa_audit_file(subj_cred,
|
||||
profile, perms, op, request, name, NULL, NULL,
|
||||
cond->uid, NULL, e);
|
||||
cond->uid, NULL, e, prompt);
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +371,7 @@ static int profile_path_perm(const char *op, const struct cred *subj_cred,
|
||||
struct aa_profile *profile,
|
||||
const struct path *path, char *buffer, u32 request,
|
||||
struct path_cond *cond, int flags,
|
||||
struct aa_perms *perms)
|
||||
struct aa_perms *perms, bool prompt)
|
||||
{
|
||||
const char *name;
|
||||
int error;
|
||||
@@ -267,7 +385,7 @@ static int profile_path_perm(const char *op, const struct cred *subj_cred,
|
||||
if (error)
|
||||
return error;
|
||||
return __aa_path_perm(op, subj_cred, profile, name, request, cond,
|
||||
flags, perms);
|
||||
flags, perms, prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -298,8 +416,9 @@ int aa_path_perm(const char *op, const struct cred *subj_cred,
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
error = fn_for_each_confined(label, profile,
|
||||
profile_path_perm(op, subj_cred, profile, path, buffer,
|
||||
request, cond, flags, &perms));
|
||||
profile_path_perm(op, subj_cred, profile, path,
|
||||
buffer, request,
|
||||
cond, flags, &perms, true));
|
||||
|
||||
aa_put_buffer(buffer);
|
||||
|
||||
@@ -409,9 +528,9 @@ done_tests:
|
||||
error = 0;
|
||||
|
||||
audit:
|
||||
return aa_audit_file(subj_cred,
|
||||
profile, &lperms, OP_LINK, request, lname, tname,
|
||||
NULL, cond->uid, info, error);
|
||||
return aa_audit_file(subj_cred, profile, &lperms, OP_LINK, request,
|
||||
lname, tname,
|
||||
NULL, cond->uid, info, error, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,7 +633,7 @@ static int __file_path_perm(const char *op, const struct cred *subj_cred,
|
||||
error = fn_for_each_not_in_set(flabel, label, profile,
|
||||
profile_path_perm(op, subj_cred, profile,
|
||||
&file->f_path, buffer,
|
||||
request, &cond, flags, &perms));
|
||||
request, &cond, flags, &perms, false));
|
||||
if (denied && !error) {
|
||||
/*
|
||||
* check every profile in file label that was not tested
|
||||
@@ -529,13 +648,13 @@ static int __file_path_perm(const char *op, const struct cred *subj_cred,
|
||||
profile_path_perm(op, subj_cred,
|
||||
profile, &file->f_path,
|
||||
buffer, request, &cond, flags,
|
||||
&perms));
|
||||
&perms, false));
|
||||
else
|
||||
error = fn_for_each_not_in_set(label, flabel, profile,
|
||||
profile_path_perm(op, subj_cred,
|
||||
profile, &file->f_path,
|
||||
buffer, request, &cond, flags,
|
||||
&perms));
|
||||
&perms, false));
|
||||
}
|
||||
if (!error)
|
||||
update_file_ctx(file_ctx(file), label, request);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "file.h"
|
||||
#include "label.h"
|
||||
#include "notify.h"
|
||||
|
||||
extern const char *const audit_mode_names[];
|
||||
#define AUDIT_MAX_INDEX 5
|
||||
@@ -38,6 +39,7 @@ enum audit_type {
|
||||
AUDIT_APPARMOR_STATUS,
|
||||
AUDIT_APPARMOR_ERROR,
|
||||
AUDIT_APPARMOR_KILL,
|
||||
AUDIT_APPARMOR_USER,
|
||||
AUDIT_APPARMOR_AUTO
|
||||
};
|
||||
|
||||
@@ -119,6 +121,9 @@ struct apparmor_audit_data {
|
||||
const char *info;
|
||||
u32 request;
|
||||
u32 denied;
|
||||
|
||||
struct task_struct *subjtsk;
|
||||
|
||||
union {
|
||||
/* these entries require a custom callback fn */
|
||||
struct {
|
||||
@@ -171,6 +176,7 @@ struct apparmor_audit_data {
|
||||
struct aa_audit_node {
|
||||
struct apparmor_audit_data data;
|
||||
struct list_head list;
|
||||
struct aa_knotif knotif;
|
||||
};
|
||||
extern struct kmem_cache *aa_audit_slab;
|
||||
|
||||
@@ -197,6 +203,9 @@ struct aa_audit_node *aa_audit_cache_find(struct aa_audit_cache *cache,
|
||||
struct apparmor_audit_data *ad);
|
||||
struct aa_audit_node *aa_audit_cache_insert(struct aa_audit_cache *cache,
|
||||
struct aa_audit_node *node);
|
||||
void aa_audit_cache_update_ent(struct aa_audit_cache *cache,
|
||||
struct aa_audit_node *node,
|
||||
struct apparmor_audit_data *data);
|
||||
void aa_audit_cache_destroy(struct aa_audit_cache *cache);
|
||||
|
||||
|
||||
@@ -210,6 +219,7 @@ void aa_audit_cache_destroy(struct aa_audit_cache *cache);
|
||||
struct apparmor_audit_data NAME = { \
|
||||
.class = (C), \
|
||||
.op = (X), \
|
||||
.subjtsk = NULL, \
|
||||
.common.type = (T), \
|
||||
.common.u.tsk = NULL, \
|
||||
.common.apparmor_audit_data = &NAME, \
|
||||
|
||||
@@ -75,7 +75,7 @@ int aa_audit_file(const struct cred *cred,
|
||||
struct aa_profile *profile, struct aa_perms *perms,
|
||||
const char *op, u32 request, const char *name,
|
||||
const char *target, struct aa_label *tlabel, kuid_t ouid,
|
||||
const char *info, int error);
|
||||
const char *info, int error, bool prompt);
|
||||
|
||||
struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules,
|
||||
aa_state_t state, struct path_cond *cond);
|
||||
@@ -86,7 +86,7 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
|
||||
int __aa_path_perm(const char *op, const struct cred *subj_cred,
|
||||
struct aa_profile *profile, const char *name,
|
||||
u32 request, struct path_cond *cond, int flags,
|
||||
struct aa_perms *perms);
|
||||
struct aa_perms *perms, bool prompt);
|
||||
int aa_path_perm(const char *op, const struct cred *subj_cred,
|
||||
struct aa_label *label, const struct path *path,
|
||||
int flags, u32 request, struct path_cond *cond);
|
||||
|
||||
@@ -18,20 +18,26 @@
|
||||
|
||||
extern struct aa_dfa *stacksplitdfa;
|
||||
|
||||
#define list_add_entry(ent, list, member) list_add(&(ent)->member, (list))
|
||||
#define list_add_tail_entry(ent, list, member) list_add_tail(&(ent)->member, (list))
|
||||
|
||||
/*
|
||||
* split individual debug cases out in preparation for finer grained
|
||||
* debug controls in the future.
|
||||
*/
|
||||
#define dbg_printk(__fmt, __args...) pr_debug(__fmt, ##__args)
|
||||
|
||||
#define DEBUG_PROMPT 2
|
||||
|
||||
#define DEBUG_NONE 0
|
||||
#define DEBUG_LABEL_ABS_ROOT 1
|
||||
#define DEBUG_LABEL 2
|
||||
#define DEBUG_DOMAIN 4
|
||||
#define DEBUG_POLICY 8
|
||||
#define DEBUG_INTERFACE 0x10
|
||||
#define DEBUG_UPCALL 0x20
|
||||
|
||||
#define DEBUG_ALL 0x1f /* update if new DEBUG_X added */
|
||||
#define DEBUG_ALL 0x3f /* update if new DEBUG_X added */
|
||||
#define DEBUG_PARSE_ERROR (-1)
|
||||
|
||||
#define DEBUG_ON (aa_g_debug != DEBUG_NONE)
|
||||
@@ -42,6 +48,7 @@ extern struct aa_dfa *stacksplitdfa;
|
||||
if (aa_g_debug & opt) \
|
||||
pr_warn_ratelimited("%s: " fmt, __func__, ##args); \
|
||||
} while (0)
|
||||
#define AA_DEBUG_ON(C, args...) do { if (C) AA_DEBUG(args); } while (0)
|
||||
#define AA_DEBUG_LABEL(LAB, X, fmt, args) \
|
||||
do { \
|
||||
if ((LAB)->flags & FLAG_DEBUG1) \
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor notifications function definitions.
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
|
||||
#ifndef __AA_NOTIFY_H
|
||||
#define __AA_NOTIFY_H
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/lsm_audit.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include <uapi/linux/apparmor.h>
|
||||
|
||||
#include "match.h"
|
||||
|
||||
struct aa_ns;
|
||||
struct aa_audit_node;
|
||||
struct apparmor_audit_data;
|
||||
|
||||
struct aa_listener {
|
||||
struct kref count;
|
||||
spinlock_t lock;
|
||||
wait_queue_head_t wait;
|
||||
struct list_head ns_proxies; /* aa_listener_proxy */
|
||||
struct list_head notifications; /* aa_audit_proxy */
|
||||
struct list_head pending; /* aa_audit_proxy */
|
||||
struct aa_ns *ns; /* counted - ns of listener */
|
||||
struct aa_dfa *filter;
|
||||
u64 last_id;
|
||||
u32 mask;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
struct aa_listener_proxy {
|
||||
struct aa_ns *ns; /* counted - ns listening to */
|
||||
struct aa_listener *listener;
|
||||
struct list_head llist;
|
||||
struct list_head nslist;
|
||||
};
|
||||
|
||||
/* need to split knofif into audit_proxy
|
||||
* prompt notifications only go to first taker so no need for completion
|
||||
* in the proxy, it increases size of proxy in non-prompt case
|
||||
*/
|
||||
struct aa_knotif {
|
||||
struct apparmor_audit_data *ad; /* counted */
|
||||
struct list_head list;
|
||||
struct completion ready;
|
||||
u64 id;
|
||||
u16 ntype;
|
||||
};
|
||||
|
||||
void aa_free_listener_proxy(struct aa_listener_proxy *proxy);
|
||||
bool aa_register_listener_proxy(struct aa_listener *listener, struct aa_ns *ns);
|
||||
struct aa_listener *aa_new_listener(struct aa_ns *ns, gfp_t gfp);
|
||||
struct aa_knotif *__aa_find_notif(struct aa_listener *listener, u64 id);
|
||||
int aa_do_notification(u16 ntype, struct aa_audit_node *node,
|
||||
bool *cache_response);
|
||||
|
||||
long aa_listener_unotif_recv(struct aa_listener *listener, void __user *buf,
|
||||
u16 max_size);
|
||||
long aa_listener_unotif_response(struct aa_listener *listener,
|
||||
struct apparmor_notif_resp *uresp,
|
||||
u16 size);
|
||||
|
||||
void aa_listener_kref(struct kref *kref);
|
||||
|
||||
static inline struct aa_listener *aa_get_listener(struct aa_listener *listener)
|
||||
{
|
||||
if (listener)
|
||||
kref_get(&(listener->count));
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
||||
static inline void aa_put_listener(struct aa_listener *listener)
|
||||
{
|
||||
if (listener)
|
||||
kref_put(&listener->count, aa_listener_kref);
|
||||
}
|
||||
|
||||
|
||||
#endif /* __AA_NOTIFY_H */
|
||||
@@ -12,6 +12,7 @@
|
||||
#define __AA_NAMESPACE_H
|
||||
|
||||
#include <linux/kref.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include "apparmor.h"
|
||||
#include "apparmorfs.h"
|
||||
@@ -42,6 +43,12 @@ struct aa_ns_acct {
|
||||
* @uniq_null: uniq value used for null learning profiles
|
||||
* @uniq_id: a unique id count for the profiles in the namespace
|
||||
* @level: level of ns within the tree hierarchy
|
||||
* @revision: policy revision for this ns
|
||||
* @wait: waitq for tasks waiting on revision changes
|
||||
* @listener_lock: lock for listeners
|
||||
* @listeners: notification listeners' proxies list
|
||||
* @labels: all the labels associated with this ns
|
||||
* @rawdata_list: raw policy data for policy
|
||||
* @dents: dentries for the namespaces file entries in apparmorfs
|
||||
*
|
||||
* An aa_ns defines the set profiles that are searched to determine which
|
||||
@@ -65,9 +72,13 @@ struct aa_ns {
|
||||
atomic_t uniq_null;
|
||||
long uniq_id;
|
||||
int level;
|
||||
|
||||
long revision;
|
||||
wait_queue_head_t wait;
|
||||
|
||||
spinlock_t listener_lock;
|
||||
struct list_head listeners;
|
||||
|
||||
struct aa_labelset labels;
|
||||
struct list_head rawdata_list;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ struct val_table_ent debug_values_table[] = {
|
||||
{ "domain", DEBUG_DOMAIN },
|
||||
{ "policy", DEBUG_POLICY },
|
||||
{ "interface", DEBUG_INTERFACE },
|
||||
{ "upcall", DEBUG_UPCALL },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
@@ -519,6 +520,7 @@ int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
|
||||
}
|
||||
|
||||
if (ad) {
|
||||
// do_notification()
|
||||
ad->subj_label = &profile->label;
|
||||
ad->request = request;
|
||||
ad->denied = denied;
|
||||
|
||||
@@ -0,0 +1,614 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* AppArmor security module
|
||||
*
|
||||
* This file contains AppArmor notifications function definitions.
|
||||
*
|
||||
* Copyright 2019 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.
|
||||
*/
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/utsname.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
#include <uapi/linux/apparmor.h>
|
||||
|
||||
#include "include/cred.h"
|
||||
#include "include/lib.h"
|
||||
#include "include/notify.h"
|
||||
#include "include/policy_ns.h"
|
||||
|
||||
/* TODO: when adding listener or ns propagate, on recursive add to child ns */
|
||||
|
||||
|
||||
static void __listener_add_knotif(struct aa_listener *listener,
|
||||
struct aa_knotif *knotif)
|
||||
{
|
||||
AA_BUG(!listener);
|
||||
AA_BUG(!knotif);
|
||||
lockdep_assert_held(&listener->lock);
|
||||
|
||||
aa_get_listener(listener);
|
||||
list_add_tail_entry(knotif, &listener->notifications, list);
|
||||
}
|
||||
|
||||
static void __listener_del_knotif(struct aa_listener *listener,
|
||||
struct aa_knotif *knotif)
|
||||
{
|
||||
AA_BUG(!listener);
|
||||
AA_BUG(!knotif);
|
||||
lockdep_assert_held(&listener->lock);
|
||||
|
||||
list_del_init(&knotif->list);
|
||||
aa_put_listener(listener);
|
||||
}
|
||||
|
||||
void aa_free_listener_proxy(struct aa_listener_proxy *proxy)
|
||||
{
|
||||
if (proxy) {
|
||||
AA_BUG(!list_empty(&proxy->llist));
|
||||
AA_BUG(!list_empty(&proxy->nslist));
|
||||
|
||||
aa_put_ns(proxy->ns);
|
||||
/* listener is owned by file, handled there */
|
||||
kfree_sensitive(proxy);
|
||||
}
|
||||
}
|
||||
|
||||
static struct aa_listener_proxy *new_listener_proxy(struct aa_listener *listener,
|
||||
struct aa_ns *ns)
|
||||
{
|
||||
struct aa_listener_proxy *proxy;
|
||||
|
||||
AA_BUG(!listener);
|
||||
lockdep_assert_not_held(&listener->lock);
|
||||
|
||||
proxy = kzalloc(sizeof(*proxy), GFP_KERNEL);
|
||||
if (!proxy)
|
||||
return NULL;
|
||||
INIT_LIST_HEAD(&proxy->llist);
|
||||
INIT_LIST_HEAD(&proxy->nslist);
|
||||
|
||||
proxy->listener = listener;
|
||||
if (ns)
|
||||
ns = aa_get_ns(ns);
|
||||
else
|
||||
ns = aa_get_current_ns();
|
||||
proxy->ns = ns;
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
list_add_tail_entry(proxy, &listener->ns_proxies, llist);
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
spin_lock(&ns->listener_lock);
|
||||
list_add_tail_entry(proxy, &ns->listeners, nslist);
|
||||
spin_unlock(&ns->listener_lock);
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
|
||||
bool aa_register_listener_proxy(struct aa_listener *listener, struct aa_ns *ns)
|
||||
{
|
||||
struct aa_listener_proxy *proxy;
|
||||
|
||||
AA_BUG(!listener);
|
||||
|
||||
proxy = new_listener_proxy(listener, ns);
|
||||
if (!proxy)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void free_listener(struct aa_listener *listener)
|
||||
{
|
||||
struct aa_listener_proxy *proxy;
|
||||
struct aa_knotif *knotif;
|
||||
|
||||
if (!listener)
|
||||
return;
|
||||
|
||||
wake_up_interruptible_poll(&listener->wait, EPOLLIN | EPOLLRDNORM);
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
while (!list_empty(&listener->ns_proxies)) {
|
||||
proxy = list_first_entry(&listener->ns_proxies,
|
||||
struct aa_listener_proxy,
|
||||
llist);
|
||||
list_del_init(&proxy->llist);
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
spin_lock(&proxy->ns->listener_lock);
|
||||
list_del_init(&proxy->nslist);
|
||||
spin_unlock(&proxy->ns->listener_lock);
|
||||
|
||||
aa_put_ns(proxy->ns);
|
||||
kfree_sensitive(proxy);
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
}
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
while (!list_empty(&listener->notifications)) {
|
||||
knotif = list_first_entry(&listener->notifications,
|
||||
struct aa_knotif,
|
||||
list);
|
||||
__listener_del_knotif(listener, knotif);
|
||||
complete(&knotif->ready);
|
||||
}
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
while (!list_empty(&listener->pending)) {
|
||||
knotif = list_first_entry(&listener->pending,
|
||||
struct aa_knotif,
|
||||
list);
|
||||
__listener_del_knotif(listener, knotif);
|
||||
complete(&knotif->ready);
|
||||
}
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
/* todo count on audit_data */
|
||||
aa_put_ns(listener->ns);
|
||||
aa_put_dfa(listener->filter);
|
||||
|
||||
kfree_sensitive(listener);
|
||||
}
|
||||
|
||||
void aa_listener_kref(struct kref *kref)
|
||||
{
|
||||
struct aa_listener *l = container_of(kref, struct aa_listener, count);
|
||||
|
||||
free_listener(l);
|
||||
}
|
||||
|
||||
struct aa_listener *aa_new_listener(struct aa_ns *ns, gfp_t gfp)
|
||||
{
|
||||
struct aa_listener *listener = kzalloc(sizeof(*listener), gfp);
|
||||
|
||||
if (!listener)
|
||||
return NULL;
|
||||
|
||||
kref_init(&listener->count);
|
||||
spin_lock_init(&listener->lock);
|
||||
init_waitqueue_head(&listener->wait);
|
||||
INIT_LIST_HEAD(&listener->ns_proxies);
|
||||
INIT_LIST_HEAD(&listener->notifications);
|
||||
INIT_LIST_HEAD(&listener->pending);
|
||||
kref_init(&listener->count);
|
||||
|
||||
if (ns)
|
||||
ns = aa_get_ns(ns);
|
||||
else
|
||||
ns = aa_get_current_ns();
|
||||
listener->ns = ns;
|
||||
listener->last_id = 1;
|
||||
|
||||
return listener;
|
||||
}
|
||||
|
||||
static struct aa_knotif *__aa_find_notif_pending(struct aa_listener *listener,
|
||||
u64 id)
|
||||
{
|
||||
struct aa_knotif *knotif;
|
||||
|
||||
AA_BUG(!listener);
|
||||
lockdep_assert_held(&listener->lock);
|
||||
|
||||
list_for_each_entry(knotif, &listener->pending, list) {
|
||||
if (knotif->id == id)
|
||||
return knotif;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct aa_knotif *__aa_find_notif(struct aa_listener *listener, u64 id)
|
||||
{
|
||||
struct aa_knotif *knotif;
|
||||
|
||||
AA_BUG(!listener);
|
||||
lockdep_assert_held(&listener->lock);
|
||||
|
||||
list_for_each_entry(knotif, &listener->notifications, list) {
|
||||
if (knotif->id == id)
|
||||
goto out;
|
||||
}
|
||||
|
||||
knotif = __aa_find_notif_pending(listener, id);
|
||||
out:
|
||||
|
||||
return knotif;
|
||||
}
|
||||
|
||||
struct aa_knotif *listener_pop_and_hold_knotif(struct aa_listener *listener)
|
||||
{
|
||||
struct aa_knotif *knotif = NULL;
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
if (!list_empty(&listener->notifications)) {
|
||||
knotif = list_first_entry(&listener->notifications, typeof(*knotif), list);
|
||||
list_del_init(&knotif->list);
|
||||
}
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
return knotif;
|
||||
}
|
||||
|
||||
void listener_repush_knotif(struct aa_listener *listener,
|
||||
struct aa_knotif *knotif)
|
||||
{
|
||||
spin_lock(&listener->lock);
|
||||
/* listener ref held from pop and hold */
|
||||
list_add_tail_entry(knotif, &listener->notifications, list);
|
||||
spin_unlock(&listener->lock);
|
||||
wake_up_interruptible_poll(&listener->wait, EPOLLIN | EPOLLRDNORM);
|
||||
}
|
||||
|
||||
void listener_push_user_pending(struct aa_listener *listener,
|
||||
struct aa_knotif *knotif)
|
||||
{
|
||||
spin_lock(&listener->lock);
|
||||
list_add_tail_entry(knotif, &listener->pending, list);
|
||||
spin_unlock(&listener->lock);
|
||||
wake_up_interruptible_poll(&listener->wait, EPOLLOUT | EPOLLWRNORM);
|
||||
}
|
||||
|
||||
struct aa_knotif *__del_and_hold_user_pending(struct aa_listener *listener,
|
||||
u64 id)
|
||||
{
|
||||
struct aa_knotif *knotif;
|
||||
|
||||
AA_BUG(!listener);
|
||||
lockdep_assert_held(&listener->lock);
|
||||
|
||||
list_for_each_entry(knotif, &listener->pending, list) {
|
||||
if (knotif->id == id) {
|
||||
list_del_init(&knotif->list);
|
||||
return knotif;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void __listener_complete_user_pending(struct aa_listener *listener,
|
||||
struct aa_knotif *knotif,
|
||||
struct apparmor_notif_resp *uresp)
|
||||
{
|
||||
list_del_init(&knotif->list);
|
||||
|
||||
if (uresp) {
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"notif %lld: response allow/reply 0x%x/0x%x, denied/reply 0x%x/0x%x, error %d/%d",
|
||||
knotif->id, knotif->ad->request, uresp->allow,
|
||||
knotif->ad->denied, uresp->deny, knotif->ad->error,
|
||||
uresp->base.error);
|
||||
|
||||
knotif->ad->denied = uresp->deny;
|
||||
knotif->ad->request = uresp->allow | uresp->deny;
|
||||
|
||||
if (!knotif->ad->denied) {
|
||||
/* no more denial, clear the error*/
|
||||
knotif->ad->error = 0;
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"notif %lld: response allowed, clearing error\n",
|
||||
knotif->id);
|
||||
} else {
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"notif %lld: response denied returning error %d\n",
|
||||
knotif->id, knotif->ad->error);
|
||||
}
|
||||
} else {
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"notif %lld: respons bad going with: allow 0x%x, denied 0x%x, error %d",
|
||||
knotif->id, knotif->ad->request, knotif->ad->denied,
|
||||
knotif->ad->error);
|
||||
}
|
||||
complete(&knotif->ready);
|
||||
aa_put_listener(listener);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* cancelled notification message due to non-timer wake-up vs.
|
||||
* keep alive message
|
||||
* cancel notification because ns removed?
|
||||
* - proxy pins ns
|
||||
* - ns can remove its list of proxies
|
||||
* - and remove queued notifications
|
||||
*/
|
||||
|
||||
/* TODO: allow registering on multiple namespaces */
|
||||
/* TODO: make filter access read side lockless */
|
||||
static bool notification_match(struct aa_listener *listener,
|
||||
struct aa_audit_node *ad)
|
||||
{
|
||||
if (!(listener->mask & (1 << ad->data.type)))
|
||||
return false;
|
||||
|
||||
if (!listener->filter)
|
||||
return true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dispatch_notif(struct aa_listener *listener,
|
||||
u16 ntype,
|
||||
struct aa_knotif *knotif)
|
||||
{
|
||||
AA_BUG(!listener);
|
||||
AA_BUG(!knotif);
|
||||
lockdep_assert_held(&listener->lock);
|
||||
|
||||
AA_DEBUG_ON(knotif->id, DEBUG_UPCALL,
|
||||
"redispatching notification with id %lld as new id %lld",
|
||||
knotif->id, listener->last_id);
|
||||
knotif->ntype = ntype;
|
||||
knotif->id = ++listener->last_id;
|
||||
init_completion(&knotif->ready);
|
||||
INIT_LIST_HEAD(&knotif->list);
|
||||
__listener_add_knotif(listener, knotif);
|
||||
}
|
||||
|
||||
// permissions changed in ad
|
||||
int aa_do_notification(u16 ntype, struct aa_audit_node *node,
|
||||
bool *cache_response)
|
||||
{
|
||||
struct aa_ns *ns = labels_ns(node->data.subj_label);
|
||||
struct aa_listener_proxy *proxy;
|
||||
struct aa_listener *listener;
|
||||
struct aa_knotif *knotif;
|
||||
int count = 0, err = 0;
|
||||
|
||||
AA_BUG(!node);
|
||||
AA_BUG(!ns);
|
||||
|
||||
*cache_response = false;
|
||||
knotif = &node->knotif;
|
||||
|
||||
/* TODO: make read side of list walk lockless */
|
||||
spin_lock(&ns->listener_lock);
|
||||
list_for_each_entry(proxy, &ns->listeners, nslist) {
|
||||
|
||||
AA_BUG(!proxy);
|
||||
listener = aa_get_listener(proxy->listener);
|
||||
AA_BUG(!listener);
|
||||
spin_lock(&listener->lock);
|
||||
if (!notification_match(listener, node)) {
|
||||
spin_unlock(&listener->lock);
|
||||
aa_put_listener(listener);
|
||||
continue;
|
||||
}
|
||||
/* delvier notification - dispatch determines if we break */
|
||||
dispatch_notif(listener, ntype, knotif); // count);
|
||||
spin_unlock(&listener->lock);
|
||||
AA_DEBUG(DEBUG_UPCALL, "found listener for %lld\n",
|
||||
knotif->id);
|
||||
|
||||
/* break to prompt */
|
||||
if (node->data.type == AUDIT_APPARMOR_USER) {
|
||||
spin_unlock(&ns->listener_lock);
|
||||
goto prompt;
|
||||
}
|
||||
count++;
|
||||
aa_put_listener(listener);
|
||||
}
|
||||
spin_unlock(&ns->listener_lock);
|
||||
AA_DEBUG(DEBUG_UPCALL, "%d listener matches for %lld\n", count,
|
||||
knotif->id);
|
||||
|
||||
/* count == 0 is no match found. No change to audit params
|
||||
* long term need to fold prompt perms into denied
|
||||
**/
|
||||
out:
|
||||
return err;
|
||||
|
||||
prompt:
|
||||
AA_DEBUG(DEBUG_UPCALL, "prompt doing wake_up_interruptible %lld",
|
||||
knotif->id);
|
||||
wake_up_interruptible_poll(&listener->wait, EPOLLIN | EPOLLRDNORM);
|
||||
|
||||
err = wait_for_completion_interruptible_timeout(&knotif->ready, msecs_to_jiffies(60000));
|
||||
if (err <= 0) {
|
||||
if (err == 0)
|
||||
AA_DEBUG(DEBUG_UPCALL, "prompt timed out %lld",
|
||||
knotif->id);
|
||||
else
|
||||
AA_DEBUG(DEBUG_UPCALL, "prompt errored out %lld",
|
||||
knotif->id);
|
||||
|
||||
/* ensure knotif is not on list because of early exit */
|
||||
spin_lock(&listener->lock);
|
||||
__listener_del_knotif(listener, knotif);
|
||||
spin_unlock(&listener->lock);
|
||||
/* time out is not considered an error and will fallback
|
||||
* to regular mediation
|
||||
*/
|
||||
} else {
|
||||
err = 0;
|
||||
*cache_response = true;
|
||||
spin_lock(&listener->lock);
|
||||
if (!list_empty(&knotif->list)) {
|
||||
__listener_del_knotif(listener, knotif);
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"bug prompt knotif still on listener list at notif completion %lld",
|
||||
knotif->id);
|
||||
}
|
||||
spin_unlock(&listener->lock);
|
||||
}
|
||||
aa_put_listener(listener);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
||||
static bool response_is_valid(struct apparmor_notif_resp *reply,
|
||||
struct aa_knotif *knotif)
|
||||
{
|
||||
if (reply->base.ntype != APPARMOR_NOTIF_RESP)
|
||||
return false;
|
||||
if ((knotif->ad->request | knotif->ad->denied) &
|
||||
~(reply->allow | reply->deny)) {
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"response does not cover permission bits in the upcall request/reply 0x%x/0x%x deny/reply 0x%x/0x%x",
|
||||
knotif->ad->request, reply->allow, knotif->ad->denied,
|
||||
reply->deny);
|
||||
return false;
|
||||
}
|
||||
/* TODO: this was disabled per snapd request, setup flag to do check
|
||||
* // allow bits that were never requested
|
||||
* if (reply->allow & ~knotif->ad->request) {
|
||||
* AA_DEBUG(DEBUG_UPCALL, "response allows more than requested");
|
||||
* return false;
|
||||
* }
|
||||
* // denying perms not in either permission set in the original
|
||||
* // notification
|
||||
* if (reply->deny & ~(knotif->ad->request | knotif->ad->denied)) {
|
||||
* AA_DEBUG(DEBUG_UPCALL, "response denies more than requested");
|
||||
* return false;
|
||||
* }
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
long aa_listener_unotif_response(struct aa_listener *listener,
|
||||
struct apparmor_notif_resp *uresp,
|
||||
u16 size)
|
||||
{
|
||||
struct aa_knotif *knotif = NULL;
|
||||
long ret;
|
||||
|
||||
spin_lock(&listener->lock);
|
||||
knotif = __del_and_hold_user_pending(listener, uresp->base.id);
|
||||
if (!knotif) {
|
||||
ret = -ENOENT;
|
||||
AA_DEBUG(DEBUG_UPCALL, "could not find id %lld",
|
||||
uresp->base.id);
|
||||
goto out;
|
||||
}
|
||||
if (!response_is_valid(uresp, knotif)) {
|
||||
ret = -EINVAL;
|
||||
AA_DEBUG(DEBUG_UPCALL, "response not valid");
|
||||
__listener_complete_user_pending(listener, knotif, NULL);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = size;
|
||||
|
||||
AA_DEBUG(DEBUG_UPCALL, "completing notif %lld", knotif->id);
|
||||
__listener_complete_user_pending(listener, knotif, uresp);
|
||||
out:
|
||||
spin_unlock(&listener->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static long build_unotif(struct aa_knotif *knotif, void __user *buf,
|
||||
u16 max_size)
|
||||
{
|
||||
union apparmor_notif_all unotif = { };
|
||||
struct user_namespace *user_ns;
|
||||
struct aa_profile *profile;
|
||||
int psize, nsize = 0;
|
||||
u16 size;
|
||||
|
||||
size = sizeof(unotif);
|
||||
profile = labels_profile(knotif->ad->subj_label);
|
||||
psize = strlen(profile->base.hname) + 1;
|
||||
size += psize;
|
||||
if (knotif->ad->name)
|
||||
nsize = strlen(knotif->ad->name) + 1;
|
||||
size += nsize;
|
||||
if (size > max_size)
|
||||
return -EMSGSIZE;
|
||||
|
||||
user_ns = get_user_ns(current->nsproxy->uts_ns->user_ns);
|
||||
|
||||
/* build response */
|
||||
unotif.common.len = size;
|
||||
unotif.common.version = APPARMOR_NOTIFY_VERSION;
|
||||
unotif.base.ntype = knotif->ntype;
|
||||
unotif.base.id = knotif->id;
|
||||
unotif.base.error = knotif->ad->error;
|
||||
unotif.op.allow = knotif->ad->request & knotif->ad->denied;
|
||||
unotif.op.deny = knotif->ad->denied;
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"notif %lld: sent to user read request 0x%x, denied 0x%x, error %d",
|
||||
knotif->id, knotif->ad->request, knotif->ad->denied, knotif->ad->error);
|
||||
|
||||
if (knotif->ad->subjtsk != NULL) {
|
||||
unotif.op.pid = task_pid_vnr(knotif->ad->subjtsk);
|
||||
unotif.file.suid = from_kuid(user_ns, task_uid(knotif->ad->subjtsk));
|
||||
}
|
||||
unotif.op.class = knotif->ad->class;
|
||||
unotif.op.label = sizeof(unotif);
|
||||
unotif.file.name = sizeof(unotif) + psize;
|
||||
unotif.file.ouid = from_kuid(user_ns, knotif->ad->fs.ouid);
|
||||
|
||||
put_user_ns(user_ns);
|
||||
|
||||
if (copy_to_user(buf, &unotif, sizeof(unotif)))
|
||||
return -EFAULT;
|
||||
if (copy_to_user(buf + sizeof(unotif), profile->base.hname, psize))
|
||||
return -EFAULT;
|
||||
if (copy_to_user(buf + sizeof(unotif) + psize, knotif->ad->name, nsize))
|
||||
return -EFAULT;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// TODO: output multiple messages in one recv
|
||||
long aa_listener_unotif_recv(struct aa_listener *listener, void __user *buf,
|
||||
u16 max_size)
|
||||
{
|
||||
struct aa_knotif *knotif;
|
||||
long ret;
|
||||
|
||||
repeat:
|
||||
knotif = listener_pop_and_hold_knotif(listener);
|
||||
if (!knotif) {
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
AA_DEBUG(DEBUG_UPCALL, "removed notif %lld from listener queue",
|
||||
knotif->id);
|
||||
switch (knotif->ad->class) {
|
||||
case AA_CLASS_FILE:
|
||||
ret = build_unotif(knotif, buf, max_size);
|
||||
if (ret < 0) {
|
||||
AA_DEBUG(DEBUG_UPCALL,
|
||||
"error (%ld): failed to copy to notif %lld data to user reading size %ld, maxsize %d",
|
||||
ret, knotif->id,
|
||||
sizeof(union apparmor_notif_all), max_size);
|
||||
listener_repush_knotif(listener, knotif);
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
AA_DEBUG(DEBUG_UPCALL, "unknown notification class");
|
||||
/* skip and move onto the next notification
|
||||
* release knotif
|
||||
* currently knotif cleanup handled by waking task in
|
||||
* aa_do_notification. Need to switch to refcount
|
||||
*/
|
||||
aa_put_listener(listener);
|
||||
goto repeat;
|
||||
}
|
||||
|
||||
if (knotif->ad->type == AUDIT_APPARMOR_USER) {
|
||||
AA_DEBUG(DEBUG_UPCALL, "adding notif %lld to pending", knotif->id);
|
||||
listener_push_user_pending(listener, knotif);
|
||||
} else {
|
||||
AA_DEBUG(DEBUG_UPCALL, "non-prompt audit in notif");
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
@@ -1231,7 +1231,8 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
|
||||
list_del_init(&ent->list);
|
||||
op = (!ent->old && !ent->rename) ? OP_PROF_LOAD : OP_PROF_REPL;
|
||||
|
||||
if (ent->old && ent->old->rawdata == ent->new->rawdata &&
|
||||
if (ent->old && ent->old->learning_cache.size == 0 &&
|
||||
ent->old->rawdata == ent->new->rawdata &&
|
||||
ent->new->rawdata) {
|
||||
/* dedup actual profile replacement */
|
||||
audit_policy(label, op, ns_name, ent->new->base.hname,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <uapi/linux/apparmor.h>
|
||||
|
||||
#include "include/apparmor.h"
|
||||
#include "include/cred.h"
|
||||
@@ -117,6 +118,8 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name)
|
||||
INIT_LIST_HEAD(&ns->rawdata_list);
|
||||
mutex_init(&ns->lock);
|
||||
init_waitqueue_head(&ns->wait);
|
||||
spin_lock_init(&ns->listener_lock);
|
||||
INIT_LIST_HEAD(&ns->listeners);
|
||||
|
||||
/* released by aa_free_ns() */
|
||||
ns->unconfined = alloc_unconfined("unconfined");
|
||||
|
||||
Reference in New Issue
Block a user