From a516993f0ac1694673412eb2d16a091eafa77d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20L=C3=BCssing?= Date: Thu, 13 Aug 2015 05:54:07 +0200 Subject: net: fix wrong skb_get() usage / crash in IGMP/MLD parsing code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recent refactoring of the IGMP and MLD parsing code into ipv6_mc_check_mld() / ip_mc_check_igmp() introduced a potential crash / BUG() invocation for bridges: I wrongly assumed that skb_get() could be used as a simple reference counter for an skb which is not the case. skb_get() bears additional semantics, a user count. This leads to a BUG() invocation in pskb_expand_head() / kernel panic if pskb_may_pull() is called on an skb with a user count greater than one - unfortunately the refactoring did just that. Fixing this by removing the skb_get() call and changing the API: The caller of ipv6_mc_check_mld() / ip_mc_check_igmp() now needs to additionally check whether the returned skb_trimmed is a clone. Fixes: 9afd85c9e455 ("net: Export IGMP/MLD message validation code") Reported-by: Brenden Blanco Signed-off-by: Linus Lüssing Acked-by: Alexei Starovoitov Signed-off-by: David S. Miller --- net/ipv4/igmp.c | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) (limited to 'net/ipv4') diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index 651cdf648ec4..9fdfd9deac11 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -1435,33 +1435,35 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) struct sk_buff *skb_chk; unsigned int transport_len; unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr); - int ret; + int ret = -EINVAL; transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb); - skb_get(skb); skb_chk = skb_checksum_trimmed(skb, transport_len, ip_mc_validate_checksum); if (!skb_chk) - return -EINVAL; + goto err; - if (!pskb_may_pull(skb_chk, len)) { - kfree_skb(skb_chk); - return -EINVAL; - } + if (!pskb_may_pull(skb_chk, len)) + goto err; ret = ip_mc_check_igmp_msg(skb_chk); - if (ret) { - kfree_skb(skb_chk); - return ret; - } + if (ret) + goto err; if (skb_trimmed) *skb_trimmed = skb_chk; - else + /* free now unneeded clone */ + else if (skb_chk != skb) kfree_skb(skb_chk); - return 0; + ret = 0; + +err: + if (ret && skb_chk && skb_chk != skb) + kfree_skb(skb_chk); + + return ret; } /** @@ -1470,7 +1472,7 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) * * Checks whether an IPv4 packet is a valid IGMP packet. If so sets - * skb network and transport headers accordingly and returns zero. + * skb transport header accordingly and returns zero. * * -EINVAL: A broken packet was detected, i.e. it violates some internet * standard @@ -1485,7 +1487,8 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) * to leave the original skb and its full frame unchanged (which might be * desirable for layer 2 frame jugglers). * - * The caller needs to release a reference count from any returned skb_trimmed. + * Caller needs to set the skb network header and free any returned skb if it + * differs from the provided skb. */ int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) { -- cgit v1.2.3