1063 lines
28 KiB
C
1063 lines
28 KiB
C
// 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/audit.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 */
|
|
|
|
// TODO: currently all knotif will have audit_node but not all in future
|
|
static inline struct aa_knotif *aa_get_knotif(struct aa_knotif *knotif)
|
|
{
|
|
if (knotif)
|
|
aa_get_audit_node(container_of(knotif, struct aa_audit_node,
|
|
knotif));
|
|
|
|
return knotif;
|
|
}
|
|
|
|
static inline void aa_put_knotif(struct aa_knotif *knotif)
|
|
{
|
|
if (knotif)
|
|
aa_put_audit_node(container_of(knotif, struct aa_audit_node,
|
|
knotif));
|
|
}
|
|
|
|
static void put_refs(struct aa_listener *listener, struct aa_knotif *knotif)
|
|
{
|
|
aa_put_listener(listener);
|
|
aa_put_knotif(knotif);
|
|
}
|
|
|
|
static void get_refs(struct aa_listener *listener, struct aa_knotif *knotif)
|
|
{
|
|
aa_get_listener(listener);
|
|
aa_get_knotif(knotif);
|
|
}
|
|
|
|
static void __knotif_del_and_hold(struct aa_knotif *knotif)
|
|
{
|
|
list_del_init(&knotif->list);
|
|
knotif->flags &= ~KNOTIF_ON_LIST;
|
|
/* keep list refcounts */
|
|
}
|
|
|
|
static void __list_append_held(struct list_head *lh, struct aa_knotif *knotif)
|
|
{
|
|
AA_BUG(!lh);
|
|
AA_BUG(!knotif);
|
|
|
|
list_add_tail_entry(knotif, lh, list);
|
|
knotif->flags |= KNOTIF_ON_LIST;
|
|
}
|
|
|
|
/*
|
|
static void __list_push_held(struct list_head *lh, struct aa_knotif *knotif)
|
|
{
|
|
AA_BUG(!lh);
|
|
AA_BUG(!knotif);
|
|
|
|
list_add_entry(knotif, lh, list);
|
|
knotif->flags |= KNOTIF_ON_LIST;
|
|
}
|
|
*/
|
|
|
|
static void __listener_add_knotif(struct aa_listener *listener,
|
|
struct aa_knotif *knotif)
|
|
{
|
|
AA_BUG(!listener);
|
|
AA_BUG(!knotif);
|
|
lockdep_assert_held(&listener->lock);
|
|
|
|
get_refs(listener, knotif);
|
|
__list_append_held(&listener->notifications, knotif);
|
|
}
|
|
|
|
// drops refs
|
|
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);
|
|
if (knotif->flags & KNOTIF_ON_LIST) {
|
|
knotif->flags &= ~KNOTIF_ON_LIST;
|
|
put_refs(listener, knotif);
|
|
}
|
|
}
|
|
|
|
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);
|
|
put_refs(listener, knotif);
|
|
}
|
|
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);
|
|
put_refs(listener, knotif);
|
|
}
|
|
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;
|
|
}
|
|
|
|
// don't drop refcounts
|
|
static 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);
|
|
__knotif_del_and_hold(knotif);
|
|
}
|
|
spin_unlock(&listener->lock);
|
|
|
|
return knotif;
|
|
}
|
|
|
|
// require refcounts held
|
|
/*
|
|
static void listener_push_held_knotif(struct aa_listener *listener,
|
|
struct aa_knotif *knotif)
|
|
{
|
|
spin_lock(&listener->lock);
|
|
// listener ref held from pop and hold
|
|
__list_push_held(&listener->notifications, knotif);
|
|
spin_unlock(&listener->lock);
|
|
wake_up_interruptible_poll(&listener->wait, EPOLLIN | EPOLLRDNORM);
|
|
}
|
|
*/
|
|
|
|
// require refcounts held
|
|
// list of knotifs waiting for response
|
|
static void listener_append_held_user_pending(struct aa_listener *listener,
|
|
struct aa_knotif *knotif)
|
|
{
|
|
spin_lock(&listener->lock);
|
|
__list_append_held(&listener->pending, knotif);
|
|
spin_unlock(&listener->lock);
|
|
//extraneous wakeup, called after reading notification
|
|
//wake_up_interruptible_poll(&listener->wait, EPOLLOUT | EPOLLWRNORM);
|
|
}
|
|
|
|
// don't drop refcounts
|
|
static 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) {
|
|
__knotif_del_and_hold(knotif);
|
|
return knotif;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************** kernel dispatching notification ********************/
|
|
|
|
/*
|
|
* 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 */
|
|
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) {
|
|
aa_state_t state;
|
|
unsigned int mask;
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "using filter");
|
|
if (!aa_ns_visible(listener->ns, labels_ns(ad->data.subj_label),
|
|
false))
|
|
return false;
|
|
state = aa_dfa_next(listener->filter, DFA_START, ad->data.type);
|
|
state = aa_dfa_match(listener->filter, state, ad->data.subj_label->hname);
|
|
if (!state)
|
|
return false;
|
|
state = aa_dfa_null_transition(listener->filter, state);
|
|
state = aa_dfa_match_u16(listener->filter, state, ad->data.class);
|
|
mask = ACCEPT_TABLE(listener->filter)[state];
|
|
if (ad->data.request & mask)
|
|
return true;
|
|
|
|
/* allow for enhanced match conditions in the future
|
|
* if (mask & AA_MATCH_CONT) {
|
|
* // TODO: match extensions
|
|
* }
|
|
*/
|
|
return false;
|
|
}
|
|
AA_DEBUG(DEBUG_UPCALL, "matched type mask filter");
|
|
return true;
|
|
}
|
|
|
|
/* Add a notification to the listener queue and wake up listener??? */
|
|
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,
|
|
"id %lld: redispatching notification as new id %lld",
|
|
knotif->id, listener->last_id);
|
|
knotif->ntype = ntype;
|
|
knotif->id = ++listener->last_id;
|
|
knotif->flags = 0;
|
|
// only needed if syncrhonous notit
|
|
init_completion(&knotif->ready);
|
|
INIT_LIST_HEAD(&knotif->list);
|
|
__listener_add_knotif(listener, knotif);
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: %s wake_up_interruptible",
|
|
knotif->id, __func__);
|
|
wake_up_interruptible_poll(&listener->wait, EPOLLIN | EPOLLRDNORM);
|
|
}
|
|
|
|
|
|
/* handle waiting for a user space reply to a notification
|
|
* Returns: <0 : error or -ERESTARTSYS if interrupted
|
|
* 0 : success
|
|
*/
|
|
static int handle_synchronous_notif(struct aa_listener *listener,
|
|
struct aa_knotif *knotif)
|
|
{
|
|
long werr;
|
|
int err;
|
|
|
|
if (knotif->ad->subj_label->flags & FLAG_INTERRUPTIBLE)
|
|
werr = wait_for_completion_interruptible_timeout(&knotif->ready,
|
|
msecs_to_jiffies(60000));
|
|
else
|
|
/* do not use close to long jiffies so cast is safe */
|
|
werr = (long) wait_for_completion_timeout(&knotif->ready,
|
|
msecs_to_jiffies(60000));
|
|
/* time out OR interrupt */
|
|
if (werr <= 0) {
|
|
/* ensure knotif is not on list because of early exit */
|
|
spin_lock(&listener->lock);
|
|
// puts refs but still have calling refs
|
|
__listener_del_knotif(listener, knotif);
|
|
spin_unlock(&listener->lock);
|
|
if (werr == 0) {
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: prompt timed out",
|
|
knotif->id);
|
|
//err = -1; // TODO: ???;
|
|
err = 0;
|
|
} else if (werr == -ERESTARTSYS) {
|
|
// interrupt fired syscall needs to be restarted
|
|
// instead of mediated
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: prompt interrupted, error %ld",
|
|
knotif->id, werr);
|
|
err = -ERESTARTSYS;
|
|
} else {
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: prompt errored out error %ld",
|
|
knotif->id, werr);
|
|
err = (int) werr;
|
|
}
|
|
/* time out is not considered an error and will fallback
|
|
* to regular mediation
|
|
*/
|
|
} else {
|
|
err = 0;
|
|
spin_lock(&listener->lock);
|
|
if (!list_empty(&knotif->list)) {
|
|
// puts refs but still have calling refs
|
|
__listener_del_knotif(listener, knotif);
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: bug prompt knotif still on listener list at notif completion",
|
|
knotif->id);
|
|
}
|
|
spin_unlock(&listener->lock);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
// permissions changed in ad
|
|
int aa_do_notification(u16 ntype, struct aa_audit_node *node)
|
|
{
|
|
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);
|
|
|
|
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, "id %lld: found listener\n",
|
|
knotif->id);
|
|
|
|
/* break to prompt */
|
|
if (node->data.type == AUDIT_APPARMOR_USER) {
|
|
spin_unlock(&ns->listener_lock);
|
|
err = handle_synchronous_notif(listener, knotif);
|
|
aa_put_listener(listener);
|
|
return err;
|
|
}
|
|
count++;
|
|
aa_put_listener(listener);
|
|
}
|
|
spin_unlock(&ns->listener_lock);
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: %d listener matches\n",
|
|
knotif->id, count);
|
|
|
|
/* count == 0 is no match found. No change to audit params
|
|
* long term need to fold prompt perms into denied
|
|
**/
|
|
return err;
|
|
}
|
|
|
|
/******************** task responding to notification **********************/
|
|
|
|
// drop references
|
|
// complete anything pending on ready
|
|
static void __listener_complete_held_user_pending(struct aa_listener *listener,
|
|
struct aa_knotif *knotif)
|
|
{
|
|
AA_BUG(!listener);
|
|
lockdep_assert_held(&listener->lock);
|
|
|
|
__knotif_del_and_hold(knotif);
|
|
complete(&knotif->ready);
|
|
put_refs(listener, knotif);
|
|
}
|
|
|
|
static void listener_complete_held_user_pending(struct aa_listener *listener,
|
|
struct aa_knotif *knotif)
|
|
{
|
|
spin_lock(&listener->lock);
|
|
__listener_complete_held_user_pending(listener, knotif);
|
|
spin_unlock(&listener->lock);
|
|
}
|
|
|
|
static bool response_is_valid_perm(struct apparmor_notif_resp_perm *reply,
|
|
struct aa_knotif *knotif, u16 size)
|
|
{
|
|
if ((knotif->ad->denied) & ~(reply->allow | reply->deny)) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: response does not cover permission bits in the upcall request/reply 0x%x/0x%x deny/reply 0x%x/0x%x",
|
|
knotif->id, knotif->ad->request, reply->allow, knotif->ad->denied,
|
|
reply->deny);
|
|
return false;
|
|
}
|
|
return true;
|
|
/* 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;
|
|
* }
|
|
*/
|
|
}
|
|
|
|
static bool response_is_valid_name(struct apparmor_notif_resp_name *reply,
|
|
struct aa_knotif *knotif, u16 size)
|
|
{
|
|
long i;
|
|
|
|
if (size <= sizeof(*reply)) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: reply bad size %u < %ld",
|
|
knotif->id, size, sizeof(*reply));
|
|
return -EMSGSIZE;
|
|
}
|
|
if (reply->name < sizeof(*reply)) {
|
|
/* inside of data declared fields */
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: reply bad name offset in fields %u < %ld",
|
|
knotif->id, reply->name, sizeof(*reply));
|
|
return -EINVAL;
|
|
}
|
|
if (reply->name > size) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: reply name pasted end of data size %u > %ld",
|
|
knotif->id, reply->name, sizeof(*reply));
|
|
return -EINVAL;
|
|
}
|
|
/* currently supported flags */
|
|
if ((reply->perm.base.flags != (URESPONSE_LOOKUP | URESPONSE_PROFILE))) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: reply bad flags 0x%x expected 0x%x",
|
|
knotif->id, reply->perm.base.flags,
|
|
URESPONSE_LOOKUP | URESPONSE_PROFILE);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((reply->perm.base.flags == URESPONSE_TAILGLOB) &&
|
|
!response_is_valid_perm(&reply->perm, knotif, size)) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: reply bad tail glob perms",
|
|
knotif->id);
|
|
return false;
|
|
}
|
|
|
|
/* check name for terminating null */
|
|
for (i = reply->name - sizeof(*reply); i < size - sizeof(*reply); i++) {
|
|
if (reply->data[i] == 0)
|
|
return true;
|
|
}
|
|
/* reached end of data without finding null */
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: reply bad no terminating null on name",
|
|
knotif->id);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* base checks userspace respnse to a notification is valid */
|
|
static bool response_is_valid(union apparmor_notif_resp *reply,
|
|
struct aa_knotif *knotif, u16 size)
|
|
{
|
|
if (reply->base.ntype == APPARMOR_NOTIF_RESP_PERM)
|
|
return response_is_valid_perm(&reply->perm, knotif, size);
|
|
else if (reply->base.ntype == APPARMOR_NOTIF_RESP_NAME)
|
|
return response_is_valid_name(&reply->name, knotif, size);
|
|
else
|
|
return false;
|
|
return false;
|
|
}
|
|
|
|
|
|
/* copy uresponse into knotif */
|
|
static void knotif_update_from_uresp_perm(struct aa_knotif *knotif,
|
|
struct apparmor_notif_resp_perm *uresp)
|
|
{
|
|
u16 flags;
|
|
|
|
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 = (knotif->ad->request | uresp->allow) &
|
|
~uresp->deny;
|
|
flags = uresp->base.flags;
|
|
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,
|
|
"id %lld: respons bad going with: allow 0x%x, denied 0x%x, error %d",
|
|
knotif->id, knotif->ad->request, knotif->ad->denied,
|
|
knotif->ad->error);
|
|
}
|
|
|
|
if (!(flags & URESPONSE_NO_CACHE)) {
|
|
/* cache of response requested */
|
|
struct aa_audit_node *node = container_of(knotif,
|
|
struct aa_audit_node,
|
|
knotif);
|
|
struct aa_audit_node *hit;
|
|
struct aa_profile *profile = labels_profile(node->data.subj_label);
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: inserting cache entry requ 0x%x denied 0x%x",
|
|
knotif->id, node->data.request, node->data.denied);
|
|
hit = aa_audit_cache_insert(&profile->learning_cache,
|
|
node);
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: (node %p, hit %p) cache insert %s: name %s node %s\n",
|
|
knotif->id, node, hit, hit != node ? "entry already exists" : "",
|
|
hit->data.name, node->data.name);
|
|
if (hit != node) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: updating existing cache entry",
|
|
knotif->id);
|
|
aa_audit_cache_update_ent(&profile->learning_cache,
|
|
hit, &node->data);
|
|
aa_put_audit_node(hit);
|
|
} else {
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "inserted into cache");
|
|
}
|
|
/* now to audit */
|
|
} /* cache_response */
|
|
}
|
|
|
|
|
|
void aa_free_ruleset(struct aa_ruleset *rules)
|
|
{
|
|
if (!rules)
|
|
return;
|
|
aa_put_pdb(rules->policy);
|
|
aa_put_pdb(rules->file);
|
|
kfree_sensitive(rules);
|
|
}
|
|
|
|
struct aa_ruleset *aa_new_ruleset(gfp_t gfp)
|
|
{
|
|
struct aa_ruleset *rules = kzalloc(sizeof(*rules), gfp);
|
|
|
|
INIT_LIST_HEAD(&rules->list);
|
|
|
|
return rules;
|
|
}
|
|
|
|
struct aa_ruleset *aa_clone_ruleset(struct aa_ruleset *rules)
|
|
{
|
|
struct aa_ruleset *clone;
|
|
|
|
clone = aa_new_ruleset(GFP_KERNEL);
|
|
if (!clone)
|
|
return NULL;
|
|
clone->size = rules->size;
|
|
clone->policy = aa_get_pdb(rules->policy);
|
|
clone->file = aa_get_pdb(rules->file);
|
|
clone->caps = rules->caps;
|
|
clone->rlimits = rules->rlimits;
|
|
|
|
/* TODO: secmark */
|
|
return clone;
|
|
}
|
|
|
|
static long knotif_update_from_uresp_name(struct aa_knotif *knotif,
|
|
struct apparmor_notif_resp_name *reply,
|
|
u16 size)
|
|
{
|
|
struct aa_ruleset *rules;
|
|
struct aa_profile *profile;
|
|
struct aa_ns *ns;
|
|
char *name, *glob;
|
|
struct aa_audit_node *clone;
|
|
struct aa_audit_node *node = container_of(knotif,
|
|
struct aa_audit_node,
|
|
knotif);
|
|
|
|
ns = aa_get_current_ns();
|
|
name = (char *) &reply->data[reply->name - sizeof(*reply)];
|
|
|
|
if (reply->perm.base.flags == (URESPONSE_LOOKUP | URESPONSE_PROFILE)) {
|
|
profile = aa_lookup_profile(ns, name);
|
|
if (!profile) {
|
|
aa_put_ns(ns);
|
|
return -ENOENT;
|
|
}
|
|
aa_put_ns(ns);
|
|
|
|
rules = aa_clone_ruleset(list_first_entry(&profile->rules,
|
|
typeof(*rules), list));
|
|
if (!rules) {
|
|
aa_put_profile(profile);
|
|
return -ENOMEM;
|
|
}
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: cloned profile '%s' rule set", knotif->id,
|
|
profile->base.hname);
|
|
aa_put_profile(profile);
|
|
|
|
/* add list to profile rules TODO: improve locking*/
|
|
profile = labels_profile(node->data.subj_label);
|
|
list_add_tail_entry(rules, &profile->rules, list);
|
|
} else if (reply->perm.base.flags == URESPONSE_TAILGLOB) {
|
|
// TODO: dedup with cache update in perm
|
|
struct aa_audit_node *node = container_of(knotif,
|
|
struct aa_audit_node,
|
|
knotif);
|
|
struct aa_audit_node *hit;
|
|
struct aa_profile *profile = labels_profile(node->data.subj_label);
|
|
|
|
clone = aa_dup_audit_data(&node->data, GFP_KERNEL);
|
|
glob = kstrdup(name, GFP_KERNEL);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
if (!clone) {
|
|
kfree(name);
|
|
return -ENOMEM;
|
|
}
|
|
kfree(clone->data.name);
|
|
clone->data.name = glob;
|
|
clone->data.flags = AUDIT_TAILGLOB_NAME;
|
|
clone->knotif.id = knotif->id;
|
|
clone->knotif.ntype = knotif->ntype;
|
|
node = clone;
|
|
knotif = &clone->knotif;
|
|
|
|
// now add it to the cache
|
|
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, reply->perm.allow,
|
|
knotif->ad->denied, reply->perm.deny, knotif->ad->error,
|
|
reply->base.error);
|
|
|
|
knotif->ad->denied = reply->perm.deny;
|
|
knotif->ad->request = reply->perm.allow | reply->perm.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);
|
|
}
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: inserting cache entry requ 0x%x denied 0x%x",
|
|
knotif->id, node->data.request, node->data.denied);
|
|
hit = aa_audit_cache_insert(&profile->learning_cache,
|
|
node);
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: cache insert %s: name %s node %s\n",
|
|
knotif->id, hit != node ? "lost race" : "",
|
|
hit->data.name, node->data.name);
|
|
if (hit != node) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: updating existing cache entry",
|
|
knotif->id);
|
|
aa_audit_cache_update_ent(&profile->learning_cache,
|
|
hit, &node->data);
|
|
aa_put_audit_node(hit);
|
|
} else {
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "inserted into cache");
|
|
}
|
|
aa_put_audit_node(clone);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/* handle userspace responding to a synchronous notification */
|
|
long aa_listener_unotif_response(struct aa_listener *listener,
|
|
union 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, size)) {
|
|
ret = -EINVAL;
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: response not valid", knotif->id);
|
|
__listener_complete_held_user_pending(listener, knotif);
|
|
goto out;
|
|
}
|
|
|
|
if (uresp->perm.base.ntype == APPARMOR_NOTIF_RESP_PERM) {
|
|
knotif_update_from_uresp_perm(knotif, &uresp->perm);
|
|
} else if (uresp->perm.base.ntype == APPARMOR_NOTIF_RESP_NAME) {
|
|
size = knotif_update_from_uresp_name(knotif, &uresp->name, size);
|
|
} else {
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: unknown response type", knotif->id);
|
|
size = -EINVAL;
|
|
}
|
|
ret = size;
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: completing notif", knotif->id);
|
|
__listener_complete_held_user_pending(listener, knotif);
|
|
out:
|
|
spin_unlock(&listener->lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/******************** task reading notification to userspace ****************/
|
|
|
|
static long append_str(void __user *pos, long remaining, const char *str)
|
|
{
|
|
long size;
|
|
|
|
if (!str)
|
|
return 0;
|
|
size = strlen(str) + 1;
|
|
if (size > remaining)
|
|
return -EMSGSIZE;
|
|
if (copy_to_user(pos, str, size))
|
|
return -EFAULT;
|
|
|
|
return size;
|
|
}
|
|
|
|
#define build_append_str(__BUF, __POS, __MAX, __STR, __FIELD, __SIZE) \
|
|
({ \
|
|
typeof(__SIZE) __tmp_size; \
|
|
__FIELD = __POS - __BUF; \
|
|
__tmp_size = append_str(__POS, max_size - (__POS - __BUF), __STR); \
|
|
if (__tmp_size >= 0) { \
|
|
__POS += __tmp_size; \
|
|
__SIZE += __tmp_size; \
|
|
} else { \
|
|
__SIZE = __tmp_size; \
|
|
} \
|
|
(__tmp_size >= 0); \
|
|
})
|
|
|
|
/* copy to userspace: notification data */
|
|
static long build_v3_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;
|
|
void __user *pos;
|
|
long size;
|
|
|
|
AA_DEBUG(DEBUG_UPCALL, "building notif max size %d", max_size);
|
|
size = sizeof(unotif);
|
|
profile = labels_profile(knotif->ad->subj_label);
|
|
AA_BUG(profile == NULL);
|
|
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.subj_uid = from_kuid(user_ns, task_uid(knotif->ad->subjtsk));
|
|
}
|
|
unotif.op.class = knotif->ad->class;
|
|
unotif.file.obj_uid = from_kuid(user_ns, knotif->ad->fs.ouid);
|
|
|
|
put_user_ns(user_ns);
|
|
|
|
pos = buf + sizeof(unotif);
|
|
if (!build_append_str(buf, pos, max_size, profile->base.hname,
|
|
unotif.op.label, size))
|
|
return -EMSGSIZE;
|
|
if (!build_append_str(buf, pos, max_size, knotif->ad->name,
|
|
unotif.file.name, size))
|
|
return -EMSGSIZE;
|
|
|
|
/* set size after appending strings */
|
|
unotif.common.len = size;
|
|
/* now the struct, at the start of user mem */
|
|
if (copy_to_user(buf, &unotif, sizeof(unotif)))
|
|
return -EFAULT;
|
|
|
|
return pos - buf;
|
|
}
|
|
|
|
// return < 0 == error
|
|
// 0 == repeat
|
|
// > 0 == built notification successfully
|
|
static long build_mediation_unotif(struct aa_listener *listener,
|
|
struct aa_knotif *knotif,
|
|
void __user *buf, u16 max_size)
|
|
{
|
|
long ret;
|
|
|
|
switch (knotif->ad->class) {
|
|
case AA_CLASS_FILE:
|
|
ret = build_v3_unotif(knotif, buf, max_size);
|
|
if (ret < 0) {
|
|
AA_DEBUG(DEBUG_UPCALL,
|
|
"id %lld: (error=%ld) failed to copy data to user reading size %ld, maxsize %d",
|
|
knotif->id, ret,
|
|
sizeof(union apparmor_notif_all), max_size);
|
|
goto out;
|
|
}
|
|
break;
|
|
default:
|
|
AA_BUG("unknown notification class");
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: unknown notification class", knotif->id);
|
|
/* skip and move onto the next notification */
|
|
return 0;
|
|
}
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/* Handle the listener reading a notification into userspace */
|
|
// 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;
|
|
|
|
do {
|
|
knotif = listener_pop_and_hold_knotif(listener);
|
|
if (!knotif) {
|
|
return -ENOENT;
|
|
}
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: removed notif from listener queue",
|
|
knotif->id);
|
|
|
|
ret = build_mediation_unotif(listener, knotif, buf, max_size);
|
|
if (ret < 0) {
|
|
/* failed - drop notif and return error to reader */
|
|
listener_complete_held_user_pending(listener, knotif);
|
|
return ret;
|
|
} else if (ret > 0) {
|
|
/* else notification copied */
|
|
break;
|
|
}
|
|
/* unknown notification: drop and try next */
|
|
listener_complete_held_user_pending(listener, knotif);
|
|
} while (ret == 0);
|
|
|
|
/* success */
|
|
if (knotif->ad->type == AUDIT_APPARMOR_USER) {
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: adding notif to pending", knotif->id);
|
|
listener_append_held_user_pending(listener, knotif);
|
|
} else {
|
|
/* no one waiting on this notification drop it */
|
|
AA_DEBUG(DEBUG_UPCALL, "id %lld: non-prompt audit notif delivered", knotif->id);
|
|
listener_complete_held_user_pending(listener, knotif);
|
|
}
|
|
|
|
return ret;
|
|
}
|