Commit dd395f67 authored by Daniel Borkmann's avatar Daniel Borkmann Committed by Ben Hutchings

ipv6: mld: fix add_grhead skb_over_panic for devs with large MTUs

commit 4c672e4b upstream.

It has been reported that generating an MLD listener report on
devices with large MTUs (e.g. 9000) and a high number of IPv6
addresses can trigger a skb_over_panic():

skbuff: skb_over_panic: text:ffffffff80612a5d len:3776 put:20
head:ffff88046d751000 data:ffff88046d751010 tail:0xed0 end:0xec0
dev:port1
 ------------[ cut here ]------------
kernel BUG at net/core/skbuff.c:100!
invalid opcode: 0000 [#1] SMP
Modules linked in: ixgbe(O)
CPU: 3 PID: 0 Comm: swapper/3 Tainted: G O 3.14.23+ #4
[...]
Call Trace:
 <IRQ>
 [<ffffffff80578226>] ? skb_put+0x3a/0x3b
 [<ffffffff80612a5d>] ? add_grhead+0x45/0x8e
 [<ffffffff80612e3a>] ? add_grec+0x394/0x3d4
 [<ffffffff80613222>] ? mld_ifc_timer_expire+0x195/0x20d
 [<ffffffff8061308d>] ? mld_dad_timer_expire+0x45/0x45
 [<ffffffff80255b5d>] ? call_timer_fn.isra.29+0x12/0x68
 [<ffffffff80255d16>] ? run_timer_softirq+0x163/0x182
 [<ffffffff80250e6f>] ? __do_softirq+0xe0/0x21d
 [<ffffffff8025112b>] ? irq_exit+0x4e/0xd3
 [<ffffffff802214bb>] ? smp_apic_timer_interrupt+0x3b/0x46
 [<ffffffff8063f10a>] ? apic_timer_interrupt+0x6a/0x70

mld_newpack() skb allocations are usually requested with dev->mtu
in size, since commit 72e09ad1 ("ipv6: avoid high order allocations")
we have changed the limit in order to be less likely to fail.

However, in MLD/IGMP code, we have some rather ugly AVAILABLE(skb)
macros, which determine if we may end up doing an skb_put() for
adding another record. To avoid possible fragmentation, we check
the skb's tailroom as skb->dev->mtu - skb->len, which is a wrong
assumption as the actual max allocation size can be much smaller.

The IGMP case doesn't have this issue as commit 57e1ab6e
("igmp: refine skb allocations") stores the allocation size in
the cb[].

Set a reserved_tailroom to make it fit into the MTU and use
skb_availroom() helper instead. This also allows to get rid of
igmp_skb_size().
Reported-by: default avatarWei Liu <lw1a2.jing@gmail.com>
Fixes: 72e09ad1 ("ipv6: avoid high order allocations")
Signed-off-by: default avatarDaniel Borkmann <dborkman@redhat.com>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Hannes Frederic Sowa <hannes@stressinduktion.org>
Cc: David L Stevens <david.stevens@oracle.com>
Acked-by: default avatarEric Dumazet <edumazet@google.com>
Acked-by: default avatarHannes Frederic Sowa <hannes@stressinduktion.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
[bwh: Backported to 3.2: adjust context]
Signed-off-by: default avatarBen Hutchings <ben@decadent.org.uk>
parent 19ad5b89
...@@ -294,9 +294,7 @@ igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted) ...@@ -294,9 +294,7 @@ igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted)
return scount; return scount;
} }
#define igmp_skb_size(skb) (*(unsigned int *)((skb)->cb)) static struct sk_buff *igmpv3_newpack(struct net_device *dev, unsigned int mtu)
static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
{ {
struct sk_buff *skb; struct sk_buff *skb;
struct rtable *rt; struct rtable *rt;
...@@ -306,6 +304,7 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size) ...@@ -306,6 +304,7 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
struct flowi4 fl4; struct flowi4 fl4;
int hlen = LL_RESERVED_SPACE(dev); int hlen = LL_RESERVED_SPACE(dev);
int tlen = dev->needed_tailroom; int tlen = dev->needed_tailroom;
unsigned int size = mtu;
while (1) { while (1) {
skb = alloc_skb(size + hlen + tlen, skb = alloc_skb(size + hlen + tlen,
...@@ -316,7 +315,6 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size) ...@@ -316,7 +315,6 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
if (size < 256) if (size < 256)
return NULL; return NULL;
} }
igmp_skb_size(skb) = size;
rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0, rt = ip_route_output_ports(net, &fl4, NULL, IGMPV3_ALL_MCR, 0,
0, 0, 0, 0,
...@@ -329,6 +327,8 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size) ...@@ -329,6 +327,8 @@ static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
skb_dst_set(skb, &rt->dst); skb_dst_set(skb, &rt->dst);
skb->dev = dev; skb->dev = dev;
skb->reserved_tailroom = skb_end_offset(skb) -
min(mtu, skb_end_offset(skb));
skb_reserve(skb, hlen); skb_reserve(skb, hlen);
skb_reset_network_header(skb); skb_reset_network_header(skb);
...@@ -398,8 +398,7 @@ static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc, ...@@ -398,8 +398,7 @@ static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc,
return skb; return skb;
} }
#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? igmp_skb_size(skb) - (skb)->len : \ #define AVAILABLE(skb) ((skb) ? skb_availroom(skb) : 0)
skb_tailroom(skb)) : 0)
static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,
int type, int gdeleted, int sdeleted) int type, int gdeleted, int sdeleted)
......
...@@ -1334,7 +1334,7 @@ mld_scount(struct ifmcaddr6 *pmc, int type, int gdeleted, int sdeleted) ...@@ -1334,7 +1334,7 @@ mld_scount(struct ifmcaddr6 *pmc, int type, int gdeleted, int sdeleted)
return scount; return scount;
} }
static struct sk_buff *mld_newpack(struct inet6_dev *idev, int size) static struct sk_buff *mld_newpack(struct inet6_dev *idev, unsigned int mtu)
{ {
struct net_device *dev = idev->dev; struct net_device *dev = idev->dev;
struct net *net = dev_net(dev); struct net *net = dev_net(dev);
...@@ -1345,13 +1345,13 @@ static struct sk_buff *mld_newpack(struct inet6_dev *idev, int size) ...@@ -1345,13 +1345,13 @@ static struct sk_buff *mld_newpack(struct inet6_dev *idev, int size)
const struct in6_addr *saddr; const struct in6_addr *saddr;
int hlen = LL_RESERVED_SPACE(dev); int hlen = LL_RESERVED_SPACE(dev);
int tlen = dev->needed_tailroom; int tlen = dev->needed_tailroom;
unsigned int size = mtu + hlen + tlen;
int err; int err;
u8 ra[8] = { IPPROTO_ICMPV6, 0, u8 ra[8] = { IPPROTO_ICMPV6, 0,
IPV6_TLV_ROUTERALERT, 2, 0, 0, IPV6_TLV_ROUTERALERT, 2, 0, 0,
IPV6_TLV_PADN, 0 }; IPV6_TLV_PADN, 0 };
/* we assume size > sizeof(ra) here */ /* we assume size > sizeof(ra) here */
size += hlen + tlen;
/* limit our allocations to order-0 page */ /* limit our allocations to order-0 page */
size = min_t(int, size, SKB_MAX_ORDER(0, 0)); size = min_t(int, size, SKB_MAX_ORDER(0, 0));
skb = sock_alloc_send_skb(sk, size, 1, &err); skb = sock_alloc_send_skb(sk, size, 1, &err);
...@@ -1359,6 +1359,8 @@ static struct sk_buff *mld_newpack(struct inet6_dev *idev, int size) ...@@ -1359,6 +1359,8 @@ static struct sk_buff *mld_newpack(struct inet6_dev *idev, int size)
if (!skb) if (!skb)
return NULL; return NULL;
skb->reserved_tailroom = skb_end_offset(skb) -
min(mtu, skb_end_offset(skb));
skb_reserve(skb, hlen); skb_reserve(skb, hlen);
if (__ipv6_get_lladdr(idev, &addr_buf, IFA_F_TENTATIVE)) { if (__ipv6_get_lladdr(idev, &addr_buf, IFA_F_TENTATIVE)) {
...@@ -1479,8 +1481,7 @@ static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc, ...@@ -1479,8 +1481,7 @@ static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc,
return skb; return skb;
} }
#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? (skb)->dev->mtu - (skb)->len : \ #define AVAILABLE(skb) ((skb) ? skb_availroom(skb) : 0)
skb_tailroom(skb)) : 0)
static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
int type, int gdeleted, int sdeleted) int type, int gdeleted, int sdeleted)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment