diff options
author | Linus Lüssing <linus.luessing@c0d3.blue> | 2015-08-12 23:54:07 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2015-08-13 20:08:39 -0400 |
commit | a516993f0ac1694673412eb2d16a091eafa77d2a (patch) | |
tree | 43e65ff360cc79cff96dc8c44f161fb0ad41b9c9 /net/ipv4/igmp.c | |
parent | 5b3e2e14eaa2a98232a4f292341fb88438685734 (diff) |
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: 9afd85c9e455 ("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>
Diffstat (limited to 'net/ipv4/igmp.c')
-rw-r--r-- | net/ipv4/igmp.c | 33 |
1 files changed, 18 insertions, 15 deletions
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) | |||
1435 | struct sk_buff *skb_chk; | 1435 | struct sk_buff *skb_chk; |
1436 | unsigned int transport_len; | 1436 | unsigned int transport_len; |
1437 | unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr); | 1437 | unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr); |
1438 | int ret; | 1438 | int ret = -EINVAL; |
1439 | 1439 | ||
1440 | transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb); | 1440 | transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb); |
1441 | 1441 | ||
1442 | skb_get(skb); | ||
1443 | skb_chk = skb_checksum_trimmed(skb, transport_len, | 1442 | skb_chk = skb_checksum_trimmed(skb, transport_len, |
1444 | ip_mc_validate_checksum); | 1443 | ip_mc_validate_checksum); |
1445 | if (!skb_chk) | 1444 | if (!skb_chk) |
1446 | return -EINVAL; | 1445 | goto err; |
1447 | 1446 | ||
1448 | if (!pskb_may_pull(skb_chk, len)) { | 1447 | if (!pskb_may_pull(skb_chk, len)) |
1449 | kfree_skb(skb_chk); | 1448 | goto err; |
1450 | return -EINVAL; | ||
1451 | } | ||
1452 | 1449 | ||
1453 | ret = ip_mc_check_igmp_msg(skb_chk); | 1450 | ret = ip_mc_check_igmp_msg(skb_chk); |
1454 | if (ret) { | 1451 | if (ret) |
1455 | kfree_skb(skb_chk); | 1452 | goto err; |
1456 | return ret; | ||
1457 | } | ||
1458 | 1453 | ||
1459 | if (skb_trimmed) | 1454 | if (skb_trimmed) |
1460 | *skb_trimmed = skb_chk; | 1455 | *skb_trimmed = skb_chk; |
1461 | else | 1456 | /* free now unneeded clone */ |
1457 | else if (skb_chk != skb) | ||
1462 | kfree_skb(skb_chk); | 1458 | kfree_skb(skb_chk); |
1463 | 1459 | ||
1464 | return 0; | 1460 | ret = 0; |
1461 | |||
1462 | err: | ||
1463 | if (ret && skb_chk && skb_chk != skb) | ||
1464 | kfree_skb(skb_chk); | ||
1465 | |||
1466 | return ret; | ||
1465 | } | 1467 | } |
1466 | 1468 | ||
1467 | /** | 1469 | /** |
@@ -1470,7 +1472,7 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) | |||
1470 | * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) | 1472 | * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) |
1471 | * | 1473 | * |
1472 | * Checks whether an IPv4 packet is a valid IGMP packet. If so sets | 1474 | * Checks whether an IPv4 packet is a valid IGMP packet. If so sets |
1473 | * skb network and transport headers accordingly and returns zero. | 1475 | * skb transport header accordingly and returns zero. |
1474 | * | 1476 | * |
1475 | * -EINVAL: A broken packet was detected, i.e. it violates some internet | 1477 | * -EINVAL: A broken packet was detected, i.e. it violates some internet |
1476 | * standard | 1478 | * standard |
@@ -1485,7 +1487,8 @@ static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) | |||
1485 | * to leave the original skb and its full frame unchanged (which might be | 1487 | * to leave the original skb and its full frame unchanged (which might be |
1486 | * desirable for layer 2 frame jugglers). | 1488 | * desirable for layer 2 frame jugglers). |
1487 | * | 1489 | * |
1488 | * The caller needs to release a reference count from any returned skb_trimmed. | 1490 | * Caller needs to set the skb network header and free any returned skb if it |
1491 | * differs from the provided skb. | ||
1489 | */ | 1492 | */ |
1490 | int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) | 1493 | int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) |
1491 | { | 1494 | { |