diff --git a/include/uapi/linux/apparmor.h b/include/uapi/linux/apparmor.h index d7dd1b771c2c..8fb8b28b2527 100644 --- a/include/uapi/linux/apparmor.h +++ b/include/uapi/linux/apparmor.h @@ -16,11 +16,12 @@ #define APPARMOR_FLAG_NOCACHE 1 enum apparmor_notif_type { - APPARMOR_NOTIF_RESP, + APPARMOR_NOTIF_RESP_PERM, APPARMOR_NOTIF_CANCEL, APPARMOR_NOTIF_INTERUPT, APPARMOR_NOTIF_ALIVE, - APPARMOR_NOTIF_OP + APPARMOR_NOTIF_OP, + APPARMOR_NOTIF_RESP_NAME, }; #define APPARMOR_NOTIFY_VERSION 3 @@ -40,6 +41,11 @@ struct apparmor_notif_filter { __u8 data[]; } __attribute__((packed)); +// flags +#define URESPONSE_NO_CACHE 1 +#define URESPONSE_LOOKUP 2 +#define URESPONSE_PROFILE 4 + struct apparmor_notif { struct apparmor_notif_common base; __u16 ntype; /* notify type */ @@ -56,13 +62,25 @@ struct apparmor_notif_update { } __attribute__((packed)); /* userspace response to notification that expects a response */ -struct apparmor_notif_resp { +struct apparmor_notif_resp_perm { struct apparmor_notif base; __s32 error; /* error if unchanged */ __u32 allow; __u32 deny; } __attribute__((packed)); +struct apparmor_notif_resp_name { + struct apparmor_notif_resp_perm perm; + __u32 name; + __u8 data[]; +} __attribute__((packed)); + +union apparmor_notif_resp { + struct apparmor_notif base; + struct apparmor_notif_resp_perm perm; + struct apparmor_notif_resp_name name; +} __attribute__((packed)); + struct apparmor_notif_op { struct apparmor_notif base; __u32 allow; @@ -75,7 +93,7 @@ struct apparmor_notif_op { struct apparmor_notif_file { struct apparmor_notif_op base; - uid_t suid, ouid; + uid_t subj_uid, obj_uid; __u32 name; /* offset into data */ __u8 data[]; @@ -87,6 +105,7 @@ union apparmor_notif_all { struct apparmor_notif base; struct apparmor_notif_op op; struct apparmor_notif_file file; + union apparmor_notif_resp respnse; }; #define APPARMOR_IOC_MAGIC 0xF8 @@ -103,6 +122,6 @@ union apparmor_notif_all { #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 *) + union apparmor_notif_resp *) #endif /* _UAPI_LINUX_APPARMOR_H */ diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 605d9b070161..35984869399b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -36,6 +36,7 @@ #include "include/policy.h" #include "include/policy_ns.h" #include "include/resource.h" +#include "include/path.h" #include "include/policy_unpack.h" #include "include/task.h" @@ -693,20 +694,37 @@ static long notify_user_recv(struct aa_listener *listener, static long notify_user_response(struct aa_listener *listener, unsigned long arg) { - struct apparmor_notif_resp uresp = {}; + union apparmor_notif_resp uresp = {}; + union apparmor_notif_resp *big_resp = NULL; + long error; 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; + if (size > aa_g_path_max) + return -EMSGSIZE; + if (size > sizeof(uresp)) { + /* TODO: put max size on message */ + big_resp = (union apparmor_notif_resp *) aa_get_buffer(false); + if (big_resp) + return -ENOMEM; + if (copy_from_user(big_resp, buf, size)) { + kfree(big_resp); + return -EFAULT; + } + } else { + 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); + error = aa_listener_unotif_response(listener, &uresp, size); + aa_put_buffer((char *) big_resp); + + return error; } - static long notify_is_id_valid(struct aa_listener *listener, unsigned long arg) { diff --git a/security/apparmor/include/notify.h b/security/apparmor/include/notify.h index d03e899885f2..32061258e852 100644 --- a/security/apparmor/include/notify.h +++ b/security/apparmor/include/notify.h @@ -76,7 +76,7 @@ int aa_do_notification(u16 ntype, struct aa_audit_node *node); 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, + union apparmor_notif_resp *uresp, u16 size); void aa_listener_kref(struct kref *kref); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index de74aab63ca6..e22c1d69b4f5 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -178,6 +178,10 @@ struct aa_ruleset { struct aa_secmark *secmark; }; +void aa_free_ruleset(struct aa_ruleset *rules); +struct aa_ruleset *aa_new_ruleset(gfp_t gfp); +struct aa_ruleset *aa_clone_ruleset(struct aa_ruleset *rules); + /* struct aa_attachment - data and rules for a profiles attachment * @list: * @xmatch_str: human readable attachment string diff --git a/security/apparmor/notify.c b/security/apparmor/notify.c index b180b50023fa..f3482d036944 100644 --- a/security/apparmor/notify.c +++ b/security/apparmor/notify.c @@ -523,12 +523,9 @@ static void listener_complete_held_user_pending(struct aa_listener *listener, spin_unlock(&listener->lock); } -/* base checks userspace respnse to a notification is valid */ -static bool response_is_valid(struct apparmor_notif_resp *reply, - struct aa_knotif *knotif) +static bool response_is_valid_perm(struct apparmor_notif_resp_perm *reply, + struct aa_knotif *knotif, u16 size) { - if (reply->base.ntype != APPARMOR_NOTIF_RESP) - return false; if ((knotif->ad->request | knotif->ad->denied) & ~(reply->allow | reply->deny)) { AA_DEBUG(DEBUG_UPCALL, @@ -537,6 +534,7 @@ static bool response_is_valid(struct apparmor_notif_resp *reply, 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) { @@ -550,14 +548,71 @@ static bool response_is_valid(struct apparmor_notif_resp *reply, * return false; * } */ - return true; } -/* copy uresponse into knotif */ -static void knotif_update_from_uresp(struct aa_knotif *knotif, - struct apparmor_notif_resp *uresp, - u16 *flags) +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; + } + /* 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 */ + return -EINVAL; + + 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", @@ -567,7 +622,7 @@ static void knotif_update_from_uresp(struct aa_knotif *knotif, knotif->ad->denied = uresp->deny; knotif->ad->request = uresp->allow | uresp->deny; - *flags = uresp->base.flags; + flags = uresp->base.flags; if (!knotif->ad->denied) { /* no more denial, clear the error*/ knotif->ad->error = 0; @@ -581,43 +636,12 @@ static void knotif_update_from_uresp(struct aa_knotif *knotif, } } else { AA_DEBUG(DEBUG_UPCALL, - "notif %lld: respons bad going with: allow 0x%x, denied 0x%x, error %d", + "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); } -} - -// move to apparmor.h -#define UNOTIF_NO_CACHE 1 - -/* handle userspace responding to a synchronous notification */ -long aa_listener_unotif_response(struct aa_listener *listener, - struct apparmor_notif_resp *uresp, - u16 size) -{ - struct aa_knotif *knotif = NULL; - u16 flags; - 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, "id %lld: response not valid", knotif->id); - __listener_complete_held_user_pending(listener, knotif); - goto out; - } - - /* handle updating actual data */ - knotif_update_from_uresp(knotif, uresp, &flags); - if (!(flags & UNOTIF_NO_CACHE)) { + if (!(flags & URESPONSE_NO_CACHE)) { /* cache of response requested */ struct aa_audit_node *node = container_of(knotif, struct aa_audit_node, @@ -644,8 +668,114 @@ long aa_listener_unotif_response(struct aa_listener *listener, } /* 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 *uresp, + u16 size) +{ + struct aa_ruleset *rules; + struct aa_profile *profile; + struct aa_ns *ns; + char *name; + struct aa_audit_node *node = container_of(knotif, + struct aa_audit_node, + knotif); + + ns = aa_get_current_ns(); + name = (char *) &uresp->data[uresp->name - sizeof(*uresp)]; + 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); + + 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); @@ -720,10 +850,10 @@ static long build_v3_unotif(struct aa_knotif *knotif, void __user *buf, 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.file.subj_uid = from_kuid(user_ns, task_uid(knotif->ad->subjtsk)); } unotif.op.class = knotif->ad->class; - unotif.file.ouid = from_kuid(user_ns, knotif->ad->fs.ouid); + unotif.file.obj_uid = from_kuid(user_ns, knotif->ad->fs.ouid); put_user_ns(user_ns);