| From foo@baz Wed Sep 30 05:25:07 CEST 2015 |
| From: Daniel Borkmann <daniel@iogearbox.net> |
| Date: Thu, 10 Sep 2015 20:05:46 +0200 |
| Subject: netlink, mmap: transform mmap skb into full skb on taps |
| |
| From: Daniel Borkmann <daniel@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 <chamaken@gmail.com> |
| Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> |
| Tested-by: Ken-ichirou MATSUZAWA <chamaken@gmail.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@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 |
| @@ -124,6 +124,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)) |
| @@ -205,7 +223,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); |
| @@ -278,11 +300,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; |
| @@ -834,7 +851,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; |