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 | |
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>
-rw-r--r-- | net/bridge/br_multicast.c | 4 | ||||
-rw-r--r-- | net/core/skbuff.c | 37 | ||||
-rw-r--r-- | net/ipv4/igmp.c | 33 | ||||
-rw-r--r-- | net/ipv6/mcast_snoop.c | 33 |
4 files changed, 56 insertions, 51 deletions
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 0b39dcc65b94..1285eaf5dc22 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c | |||
@@ -1591,7 +1591,7 @@ static int br_multicast_ipv4_rcv(struct net_bridge *br, | |||
1591 | break; | 1591 | break; |
1592 | } | 1592 | } |
1593 | 1593 | ||
1594 | if (skb_trimmed) | 1594 | if (skb_trimmed && skb_trimmed != skb) |
1595 | kfree_skb(skb_trimmed); | 1595 | kfree_skb(skb_trimmed); |
1596 | 1596 | ||
1597 | return err; | 1597 | return err; |
@@ -1636,7 +1636,7 @@ static int br_multicast_ipv6_rcv(struct net_bridge *br, | |||
1636 | break; | 1636 | break; |
1637 | } | 1637 | } |
1638 | 1638 | ||
1639 | if (skb_trimmed) | 1639 | if (skb_trimmed && skb_trimmed != skb) |
1640 | kfree_skb(skb_trimmed); | 1640 | kfree_skb(skb_trimmed); |
1641 | 1641 | ||
1642 | return err; | 1642 | return err; |
diff --git a/net/core/skbuff.c b/net/core/skbuff.c index b6a19ca0f99e..bf9a5d93c2d1 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c | |||
@@ -4022,8 +4022,8 @@ EXPORT_SYMBOL(skb_checksum_setup); | |||
4022 | * Otherwise returns the provided skb. Returns NULL in error cases | 4022 | * Otherwise returns the provided skb. Returns NULL in error cases |
4023 | * (e.g. transport_len exceeds skb length or out-of-memory). | 4023 | * (e.g. transport_len exceeds skb length or out-of-memory). |
4024 | * | 4024 | * |
4025 | * Caller needs to set the skb transport header and release the returned skb. | 4025 | * Caller needs to set the skb transport header and free any returned skb if it |
4026 | * Provided skb is consumed. | 4026 | * differs from the provided skb. |
4027 | */ | 4027 | */ |
4028 | static struct sk_buff *skb_checksum_maybe_trim(struct sk_buff *skb, | 4028 | static struct sk_buff *skb_checksum_maybe_trim(struct sk_buff *skb, |
4029 | unsigned int transport_len) | 4029 | unsigned int transport_len) |
@@ -4032,16 +4032,12 @@ static struct sk_buff *skb_checksum_maybe_trim(struct sk_buff *skb, | |||
4032 | unsigned int len = skb_transport_offset(skb) + transport_len; | 4032 | unsigned int len = skb_transport_offset(skb) + transport_len; |
4033 | int ret; | 4033 | int ret; |
4034 | 4034 | ||
4035 | if (skb->len < len) { | 4035 | if (skb->len < len) |
4036 | kfree_skb(skb); | ||
4037 | return NULL; | 4036 | return NULL; |
4038 | } else if (skb->len == len) { | 4037 | else if (skb->len == len) |
4039 | return skb; | 4038 | return skb; |
4040 | } | ||
4041 | 4039 | ||
4042 | skb_chk = skb_clone(skb, GFP_ATOMIC); | 4040 | skb_chk = skb_clone(skb, GFP_ATOMIC); |
4043 | kfree_skb(skb); | ||
4044 | |||
4045 | if (!skb_chk) | 4041 | if (!skb_chk) |
4046 | return NULL; | 4042 | return NULL; |
4047 | 4043 | ||
@@ -4066,8 +4062,8 @@ static struct sk_buff *skb_checksum_maybe_trim(struct sk_buff *skb, | |||
4066 | * If the skb has data beyond the given transport length, then a | 4062 | * If the skb has data beyond the given transport length, then a |
4067 | * trimmed & cloned skb is checked and returned. | 4063 | * trimmed & cloned skb is checked and returned. |
4068 | * | 4064 | * |
4069 | * Caller needs to set the skb transport header and release the returned skb. | 4065 | * Caller needs to set the skb transport header and free any returned skb if it |
4070 | * Provided skb is consumed. | 4066 | * differs from the provided skb. |
4071 | */ | 4067 | */ |
4072 | struct sk_buff *skb_checksum_trimmed(struct sk_buff *skb, | 4068 | struct sk_buff *skb_checksum_trimmed(struct sk_buff *skb, |
4073 | unsigned int transport_len, | 4069 | unsigned int transport_len, |
@@ -4079,23 +4075,26 @@ struct sk_buff *skb_checksum_trimmed(struct sk_buff *skb, | |||
4079 | 4075 | ||
4080 | skb_chk = skb_checksum_maybe_trim(skb, transport_len); | 4076 | skb_chk = skb_checksum_maybe_trim(skb, transport_len); |
4081 | if (!skb_chk) | 4077 | if (!skb_chk) |
4082 | return NULL; | 4078 | goto err; |
4083 | 4079 | ||
4084 | if (!pskb_may_pull(skb_chk, offset)) { | 4080 | if (!pskb_may_pull(skb_chk, offset)) |
4085 | kfree_skb(skb_chk); | 4081 | goto err; |
4086 | return NULL; | ||
4087 | } | ||
4088 | 4082 | ||
4089 | __skb_pull(skb_chk, offset); | 4083 | __skb_pull(skb_chk, offset); |
4090 | ret = skb_chkf(skb_chk); | 4084 | ret = skb_chkf(skb_chk); |
4091 | __skb_push(skb_chk, offset); | 4085 | __skb_push(skb_chk, offset); |
4092 | 4086 | ||
4093 | if (ret) { | 4087 | if (ret) |
4094 | kfree_skb(skb_chk); | 4088 | goto err; |
4095 | return NULL; | ||
4096 | } | ||
4097 | 4089 | ||
4098 | return skb_chk; | 4090 | return skb_chk; |
4091 | |||
4092 | err: | ||
4093 | if (skb_chk && skb_chk != skb) | ||
4094 | kfree_skb(skb_chk); | ||
4095 | |||
4096 | return NULL; | ||
4097 | |||
4099 | } | 4098 | } |
4100 | EXPORT_SYMBOL(skb_checksum_trimmed); | 4099 | EXPORT_SYMBOL(skb_checksum_trimmed); |
4101 | 4100 | ||
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 | { |
diff --git a/net/ipv6/mcast_snoop.c b/net/ipv6/mcast_snoop.c index df8afe5ab31e..9405b04eecc6 100644 --- a/net/ipv6/mcast_snoop.c +++ b/net/ipv6/mcast_snoop.c | |||
@@ -143,34 +143,36 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb, | |||
143 | struct sk_buff *skb_chk = NULL; | 143 | struct sk_buff *skb_chk = NULL; |
144 | unsigned int transport_len; | 144 | unsigned int transport_len; |
145 | unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); | 145 | unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); |
146 | int ret; | 146 | int ret = -EINVAL; |
147 | 147 | ||
148 | transport_len = ntohs(ipv6_hdr(skb)->payload_len); | 148 | transport_len = ntohs(ipv6_hdr(skb)->payload_len); |
149 | transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr); | 149 | transport_len -= skb_transport_offset(skb) - sizeof(struct ipv6hdr); |
150 | 150 | ||
151 | skb_get(skb); | ||
152 | skb_chk = skb_checksum_trimmed(skb, transport_len, | 151 | skb_chk = skb_checksum_trimmed(skb, transport_len, |
153 | ipv6_mc_validate_checksum); | 152 | ipv6_mc_validate_checksum); |
154 | if (!skb_chk) | 153 | if (!skb_chk) |
155 | return -EINVAL; | 154 | goto err; |
156 | 155 | ||
157 | if (!pskb_may_pull(skb_chk, len)) { | 156 | if (!pskb_may_pull(skb_chk, len)) |
158 | kfree_skb(skb_chk); | 157 | goto err; |
159 | return -EINVAL; | ||
160 | } | ||
161 | 158 | ||
162 | ret = ipv6_mc_check_mld_msg(skb_chk); | 159 | ret = ipv6_mc_check_mld_msg(skb_chk); |
163 | if (ret) { | 160 | if (ret) |
164 | kfree_skb(skb_chk); | 161 | goto err; |
165 | return ret; | ||
166 | } | ||
167 | 162 | ||
168 | if (skb_trimmed) | 163 | if (skb_trimmed) |
169 | *skb_trimmed = skb_chk; | 164 | *skb_trimmed = skb_chk; |
170 | else | 165 | /* free now unneeded clone */ |
166 | else if (skb_chk != skb) | ||
171 | kfree_skb(skb_chk); | 167 | kfree_skb(skb_chk); |
172 | 168 | ||
173 | return 0; | 169 | ret = 0; |
170 | |||
171 | err: | ||
172 | if (ret && skb_chk && skb_chk != skb) | ||
173 | kfree_skb(skb_chk); | ||
174 | |||
175 | return ret; | ||
174 | } | 176 | } |
175 | 177 | ||
176 | /** | 178 | /** |
@@ -179,7 +181,7 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb, | |||
179 | * @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional) | 181 | * @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional) |
180 | * | 182 | * |
181 | * Checks whether an IPv6 packet is a valid MLD packet. If so sets | 183 | * Checks whether an IPv6 packet is a valid MLD packet. If so sets |
182 | * skb network and transport headers accordingly and returns zero. | 184 | * skb transport header accordingly and returns zero. |
183 | * | 185 | * |
184 | * -EINVAL: A broken packet was detected, i.e. it violates some internet | 186 | * -EINVAL: A broken packet was detected, i.e. it violates some internet |
185 | * standard | 187 | * standard |
@@ -194,7 +196,8 @@ static int __ipv6_mc_check_mld(struct sk_buff *skb, | |||
194 | * to leave the original skb and its full frame unchanged (which might be | 196 | * to leave the original skb and its full frame unchanged (which might be |
195 | * desirable for layer 2 frame jugglers). | 197 | * desirable for layer 2 frame jugglers). |
196 | * | 198 | * |
197 | * The caller needs to release a reference count from any returned skb_trimmed. | 199 | * Caller needs to set the skb network header and free any returned skb if it |
200 | * differs from the provided skb. | ||
198 | */ | 201 | */ |
199 | int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) | 202 | int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) |
200 | { | 203 | { |