4.1-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Daniel Borkmann <dan...@iogearbox.net>

[ Upstream commit 1853c949646005b5959c483becde86608f548f24 ]

Ken-ichirou reported that running netlink in mmap mode for receive in
combination with nlmon will throw a NULL pointer dereference in
__kfree_skb() on nlmon_xmit(), in my case I can also trigger an "unable
to handle kernel paging request". The problem is the skb_clone() in
__netlink_deliver_tap_skb() for skbs that are mmaped.

I.e. the cloned skb doesn't have a destructor, whereas the mmap netlink
skb has it pointed to netlink_skb_destructor(), set in the handler
netlink_ring_setup_skb(). There, skb->head is being set to NULL, so
that in such cases, __kfree_skb() doesn't perform a skb_release_data()
via skb_release_all(), where skb->head is possibly being freed through
kfree(head) into slab allocator, although netlink mmap skb->head points
to the mmap buffer. Similarly, the same has to be done also for large
netlink skbs where the data area is vmalloced. Therefore, as discussed,
make a copy for these rather rare cases for now. This fixes the issue
on my and Ken-ichirou's test-cases.

Reference: http://thread.gmane.org/gmane.linux.network/371129
Fixes: bcbde0d449ed ("net: netlink: virtual tap device management")
Reported-by: Ken-ichirou MATSUZAWA <chama...@gmail.com>
Signed-off-by: Daniel Borkmann <dan...@iogearbox.net>
Tested-by: Ken-ichirou MATSUZAWA <chama...@gmail.com>
Signed-off-by: David S. Miller <da...@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 net/netlink/af_netlink.c |   30 +++++++++++++++++++++++-------
 net/netlink/af_netlink.h |    9 +++++++++
 2 files changed, 32 insertions(+), 7 deletions(-)

--- a/net/netlink/af_netlink.c
+++ b/net/netlink/af_netlink.c
@@ -123,6 +123,24 @@ static inline u32 netlink_group_mask(u32
        return group ? 1 << (group - 1) : 0;
 }
 
+static struct sk_buff *netlink_to_full_skb(const struct sk_buff *skb,
+                                          gfp_t gfp_mask)
+{
+       unsigned int len = skb_end_offset(skb);
+       struct sk_buff *new;
+
+       new = alloc_skb(len, gfp_mask);
+       if (new == NULL)
+               return NULL;
+
+       NETLINK_CB(new).portid = NETLINK_CB(skb).portid;
+       NETLINK_CB(new).dst_group = NETLINK_CB(skb).dst_group;
+       NETLINK_CB(new).creds = NETLINK_CB(skb).creds;
+
+       memcpy(skb_put(new, len), skb->data, len);
+       return new;
+}
+
 int netlink_add_tap(struct netlink_tap *nt)
 {
        if (unlikely(nt->dev->type != ARPHRD_NETLINK))
@@ -204,7 +222,11 @@ static int __netlink_deliver_tap_skb(str
        int ret = -ENOMEM;
 
        dev_hold(dev);
-       nskb = skb_clone(skb, GFP_ATOMIC);
+
+       if (netlink_skb_is_mmaped(skb) || is_vmalloc_addr(skb->head))
+               nskb = netlink_to_full_skb(skb, GFP_ATOMIC);
+       else
+               nskb = skb_clone(skb, GFP_ATOMIC);
        if (nskb) {
                nskb->dev = dev;
                nskb->protocol = htons((u16) sk->sk_protocol);
@@ -276,11 +298,6 @@ static void netlink_rcv_wake(struct sock
 }
 
 #ifdef CONFIG_NETLINK_MMAP
-static bool netlink_skb_is_mmaped(const struct sk_buff *skb)
-{
-       return NETLINK_CB(skb).flags & NETLINK_SKB_MMAPED;
-}
-
 static bool netlink_rx_is_mmaped(struct sock *sk)
 {
        return nlk_sk(sk)->rx_ring.pg_vec != NULL;
@@ -832,7 +849,6 @@ static void netlink_ring_set_copied(stru
 }
 
 #else /* CONFIG_NETLINK_MMAP */
-#define netlink_skb_is_mmaped(skb)     false
 #define netlink_rx_is_mmaped(sk)       false
 #define netlink_tx_is_mmaped(sk)       false
 #define netlink_mmap                   sock_no_mmap
--- a/net/netlink/af_netlink.h
+++ b/net/netlink/af_netlink.h
@@ -59,6 +59,15 @@ static inline struct netlink_sock *nlk_s
        return container_of(sk, struct netlink_sock, sk);
 }
 
+static inline bool netlink_skb_is_mmaped(const struct sk_buff *skb)
+{
+#ifdef CONFIG_NETLINK_MMAP
+       return NETLINK_CB(skb).flags & NETLINK_SKB_MMAPED;
+#else
+       return false;
+#endif /* CONFIG_NETLINK_MMAP */
+}
+
 struct netlink_table {
        struct rhashtable       hash;
        struct hlist_head       mc_list;


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to