netlink: netlink_recvmsg() fix
authorEric Dumazet <eric.dumazet@gmail.com>
Mon, 26 Jul 2010 20:09:16 +0000 (13:09 -0700)
committerDavid S. Miller <davem@davemloft.net>
Mon, 26 Jul 2010 20:09:16 +0000 (13:09 -0700)
commit 1dacc76d0014
(net/compat/wext: send different messages to compat tasks)
introduced a race condition on netlink, in case MSG_PEEK is used.

An skb given by skb_recv_datagram() might be shared, we must copy it
before any modification, or risk fatal corruption.

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/netlink/af_netlink.c

index 8648a9922aabace0231de8eec18b88e30239fd5f..2cbf380377d5e009fa972d85af18ded9971a5248 100644 (file)
@@ -1406,7 +1406,7 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
        struct netlink_sock *nlk = nlk_sk(sk);
        int noblock = flags&MSG_DONTWAIT;
        size_t copied;
-       struct sk_buff *skb, *frag __maybe_unused = NULL;
+       struct sk_buff *skb;
        int err;
 
        if (flags&MSG_OOB)
@@ -1441,7 +1441,21 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
                        kfree_skb(skb);
                        skb = compskb;
                } else {
-                       frag = skb_shinfo(skb)->frag_list;
+                       /*
+                        * Before setting frag_list to NULL, we must get a
+                        * private copy of skb if shared (because of MSG_PEEK)
+                        */
+                       if (skb_shared(skb)) {
+                               struct sk_buff *nskb;
+
+                               nskb = pskb_copy(skb, GFP_KERNEL);
+                               kfree_skb(skb);
+                               skb = nskb;
+                               err = -ENOMEM;
+                               if (!skb)
+                                       goto out;
+                       }
+                       kfree_skb(skb_shinfo(skb)->frag_list);
                        skb_shinfo(skb)->frag_list = NULL;
                }
        }
@@ -1478,10 +1492,6 @@ static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
        if (flags & MSG_TRUNC)
                copied = skb->len;
 
-#ifdef CONFIG_COMPAT_NETLINK_MESSAGES
-       skb_shinfo(skb)->frag_list = frag;
-#endif
-
        skb_free_datagram(sk, skb);
 
        if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2)