net: fix wrong skb_get() usage / crash in IGMP/MLD parsing code
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: 9afd85c9e4
("net: Export IGMP/MLD message validation code")
Reported-by: Brenden Blanco <bblanco@plumgrid.com>
Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Acked-by: Alexei Starovoitov <ast@plumgrid.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:

committed by
David S. Miller

parent
5b3e2e14ea
commit
a516993f0a
@@ -143,34 +143,36 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb,
|
||||
struct sk_buff *skb_chk = NULL;
|
||||
unsigned int transport_len;
|
||||
unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg);
|
||||
int ret;
|
||||
int ret = -EINVAL;
|
||||
|
||||
transport_len = ntohs(ipv6_hdr(skb)->payload_len);
|
||||
transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr);
|
||||
|
||||
skb_get(skb);
|
||||
skb_chk = skb_checksum_trimmed(skb, transport_len,
|
||||
ipv6_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 = ipv6_mc_check_mld_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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,7 +181,7 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb,
|
||||
* @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional)
|
||||
*
|
||||
* Checks whether an IPv6 packet is a valid MLD 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
|
||||
@@ -194,7 +196,8 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb,
|
||||
* 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 ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed)
|
||||
{
|
||||
|
Reference in New Issue
Block a user