Skip to content

Commit

Permalink
UBUNTU: SAUCE: apparmor: enable userspace upcall for mediation
Browse files Browse the repository at this point in the history
BugLink: https://bugs.launchpad.net/bugs/2012136

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>
Signed-off-by: Andrea Righi <andrea.righi@canonical.com>
  • Loading branch information
John Johansen authored and Andrea Righi committed Mar 23, 2023
1 parent 8ae8597 commit 53d6de0
Show file tree
Hide file tree
Showing 16 changed files with 1,187 additions and 43 deletions.
106 changes: 106 additions & 0 deletions include/uapi/linux/apparmor.h
Original file line number Diff line number Diff line change
@@ -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 */
2 changes: 1 addition & 1 deletion security/apparmor/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions security/apparmor/af_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
167 changes: 167 additions & 0 deletions security/apparmor/apparmorfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -2434,6 +2600,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),
Expand Down
29 changes: 19 additions & 10 deletions security/apparmor/audit.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,16 +307,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;
Expand Down Expand Up @@ -377,6 +372,7 @@ struct aa_audit_node *aa_dup_audit_data(struct apparmor_audit_data *orig,
if (!copy)
return NULL;

copy->knotif.ad = &copy->data;
INIT_LIST_HEAD(&copy->list);
/* copy class early so aa_free_audit_node can use switch on failure */
copy->data.class = orig->class;
Expand Down Expand Up @@ -417,12 +413,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);

Expand Down Expand Up @@ -460,8 +456,10 @@ struct aa_audit_node *aa_dup_audit_data(struct apparmor_audit_data *orig,
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; \
})

Expand Down Expand Up @@ -505,6 +503,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)
{
Expand Down
Loading

0 comments on commit 53d6de0

Please sign in to comment.