| From: Paul Moore <paul@paul-moore.com> |
| Date: Tue, 28 Apr 2020 09:59:02 -0400 |
| Subject: selinux: properly handle multiple messages in selinux_netlink_send() |
| |
| commit fb73974172ffaaf57a7c42f35424d9aece1a5af6 upstream. |
| |
| Fix the SELinux netlink_send hook to properly handle multiple netlink |
| messages in a single sk_buff; each message is parsed and subject to |
| SELinux access control. Prior to this patch, SELinux only inspected |
| the first message in the sk_buff. |
| |
| Cc: stable@vger.kernel.org |
| Reported-by: Dmitry Vyukov <dvyukov@google.com> |
| Reviewed-by: Stephen Smalley <stephen.smalley.work@gmail.com> |
| Signed-off-by: Paul Moore <paul@paul-moore.com> |
| [bwh: Backported to 3.16: adjust context] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| --- a/security/selinux/hooks.c |
| +++ b/security/selinux/hooks.c |
| @@ -4669,39 +4669,59 @@ static int selinux_tun_dev_open(void *se |
| |
| static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb) |
| { |
| - int err = 0; |
| - u32 perm; |
| + int rc = 0; |
| + unsigned int msg_len; |
| + unsigned int data_len = skb->len; |
| + unsigned char *data = skb->data; |
| struct nlmsghdr *nlh; |
| struct sk_security_struct *sksec = sk->sk_security; |
| + u16 sclass = sksec->sclass; |
| + u32 perm; |
| |
| - if (skb->len < NLMSG_HDRLEN) { |
| - err = -EINVAL; |
| - goto out; |
| - } |
| - nlh = nlmsg_hdr(skb); |
| + while (data_len >= nlmsg_total_size(0)) { |
| + nlh = (struct nlmsghdr *)data; |
| |
| - err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm); |
| - if (err) { |
| - if (err == -EINVAL) { |
| + /* NOTE: the nlmsg_len field isn't reliably set by some netlink |
| + * users which means we can't reject skb's with bogus |
| + * length fields; our solution is to follow what |
| + * netlink_rcv_skb() does and simply skip processing at |
| + * messages with length fields that are clearly junk |
| + */ |
| + if (nlh->nlmsg_len < NLMSG_HDRLEN || nlh->nlmsg_len > data_len) |
| + return 0; |
| + |
| + rc = selinux_nlmsg_lookup(sclass, nlh->nlmsg_type, &perm); |
| + if (rc == 0) { |
| + rc = sock_has_perm(current, sk, perm); |
| + if (rc) |
| + return rc; |
| + } else if (rc == -EINVAL) { |
| + /* -EINVAL is a missing msg/perm mapping */ |
| pr_warn_ratelimited("SELinux: unrecognized netlink" |
| - " message: protocol=%hu nlmsg_type=%hu sclass=%s" |
| - " pig=%d comm=%s\n", |
| - sk->sk_protocol, nlh->nlmsg_type, |
| - secclass_map[sksec->sclass - 1].name, |
| - task_pid_nr(current), current->comm); |
| - if (!selinux_enforcing || security_get_allow_unknown()) |
| - err = 0; |
| + " message: protocol=%hu nlmsg_type=%hu sclass=%s" |
| + " pid=%d comm=%s\n", |
| + sk->sk_protocol, nlh->nlmsg_type, |
| + secclass_map[sclass - 1].name, |
| + task_pid_nr(current), current->comm); |
| + if (selinux_enforcing && !security_get_allow_unknown()) |
| + return rc; |
| + rc = 0; |
| + } else if (rc == -ENOENT) { |
| + /* -ENOENT is a missing socket/class mapping, ignore */ |
| + rc = 0; |
| + } else { |
| + return rc; |
| } |
| |
| - /* Ignore */ |
| - if (err == -ENOENT) |
| - err = 0; |
| - goto out; |
| + /* move to the next message after applying netlink padding */ |
| + msg_len = NLMSG_ALIGN(nlh->nlmsg_len); |
| + if (msg_len >= data_len) |
| + return 0; |
| + data_len -= msg_len; |
| + data += msg_len; |
| } |
| |
| - err = sock_has_perm(current, sk, perm); |
| -out: |
| - return err; |
| + return rc; |
| } |
| |
| #ifdef CONFIG_NETFILTER |