Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bridge: simplify ip_mc_check_igmp() and ipv6_mc_check_mld() calls

This patch refactors ip_mc_check_igmp(), ipv6_mc_check_mld() and
their callers (more precisely, the Linux bridge) to not rely on
the skb_trimmed parameter anymore.

An skb with its tail trimmed to the IP packet length was initially
introduced for the following three reasons:

1) To be able to verify the ICMPv6 checksum.
2) To be able to distinguish the version of an IGMP or MLD query.
They are distinguishable only by their size.
3) To avoid parsing data for an IGMPv3 or MLDv2 report that is
beyond the IP packet but still within the skb.

The first case still uses a cloned and potentially trimmed skb to
verfiy. However, there is no need to propagate it to the caller.
For the second and third case explicit IP packet length checks were
added.

This hopefully makes ip_mc_check_igmp() and ipv6_mc_check_mld() easier
to read and verfiy, as well as easier to use.

Signed-off-by: Linus Lüssing <linus.luessing@c0d3.blue>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Linus Lüssing and committed by
David S. Miller
ba5ea614 6679cf09

+70 -72
+10 -1
include/linux/igmp.h
··· 18 18 #include <linux/skbuff.h> 19 19 #include <linux/timer.h> 20 20 #include <linux/in.h> 21 + #include <linux/ip.h> 21 22 #include <linux/refcount.h> 22 23 #include <uapi/linux/igmp.h> 23 24 ··· 107 106 #define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value) 108 107 #define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value) 109 108 109 + static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len) 110 + { 111 + if (skb_transport_offset(skb) + ip_transport_len(skb) < len) 112 + return -EINVAL; 113 + 114 + return pskb_may_pull(skb, len); 115 + } 116 + 110 117 extern int ip_check_mc_rcu(struct in_device *dev, __be32 mc_addr, __be32 src_addr, u8 proto); 111 118 extern int igmp_rcv(struct sk_buff *); 112 119 extern int ip_mc_join_group(struct sock *sk, struct ip_mreqn *imr); ··· 139 130 extern void ip_mc_remap(struct in_device *); 140 131 extern void ip_mc_dec_group(struct in_device *in_dev, __be32 addr); 141 132 extern void ip_mc_inc_group(struct in_device *in_dev, __be32 addr); 142 - int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed); 133 + int ip_mc_check_igmp(struct sk_buff *skb); 143 134 144 135 #endif
+5
include/linux/ip.h
··· 34 34 { 35 35 return (struct iphdr *)skb_transport_header(skb); 36 36 } 37 + 38 + static inline unsigned int ip_transport_len(const struct sk_buff *skb) 39 + { 40 + return ntohs(ip_hdr(skb)->tot_len) - skb_network_header_len(skb); 41 + } 37 42 #endif /* _LINUX_IP_H */
+6
include/linux/ipv6.h
··· 104 104 return (struct ipv6hdr *)skb_transport_header(skb); 105 105 } 106 106 107 + static inline unsigned int ipv6_transport_len(const struct sk_buff *skb) 108 + { 109 + return ntohs(ipv6_hdr(skb)->payload_len) + sizeof(struct ipv6hdr) - 110 + skb_network_header_len(skb); 111 + } 112 + 107 113 /* 108 114 This structure contains results of exthdrs parsing 109 115 as offsets from skb->nh.
+11 -1
include/net/addrconf.h
··· 49 49 struct in6_addr prefix; 50 50 }; 51 51 52 + #include <linux/ipv6.h> 52 53 #include <linux/netdevice.h> 53 54 #include <net/if_inet6.h> 54 55 #include <net/ipv6.h> ··· 202 201 /* 203 202 * multicast prototypes (mcast.c) 204 203 */ 204 + static inline int ipv6_mc_may_pull(struct sk_buff *skb, 205 + unsigned int len) 206 + { 207 + if (skb_transport_offset(skb) + ipv6_transport_len(skb) < len) 208 + return -EINVAL; 209 + 210 + return pskb_may_pull(skb, len); 211 + } 212 + 205 213 int ipv6_sock_mc_join(struct sock *sk, int ifindex, 206 214 const struct in6_addr *addr); 207 215 int ipv6_sock_mc_drop(struct sock *sk, int ifindex, ··· 229 219 void ipv6_mc_remap(struct inet6_dev *idev); 230 220 void ipv6_mc_init_dev(struct inet6_dev *idev); 231 221 void ipv6_mc_destroy_dev(struct inet6_dev *idev); 232 - int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed); 222 + int ipv6_mc_check_mld(struct sk_buff *skb); 233 223 void addrconf_dad_failure(struct sk_buff *skb, struct inet6_ifaddr *ifp); 234 224 235 225 bool ipv6_chk_mcast_addr(struct net_device *dev, const struct in6_addr *group,
+2 -2
net/batman-adv/multicast.c
··· 674 674 */ 675 675 static bool batadv_mcast_is_report_ipv4(struct sk_buff *skb) 676 676 { 677 - if (ip_mc_check_igmp(skb, NULL) < 0) 677 + if (ip_mc_check_igmp(skb) < 0) 678 678 return false; 679 679 680 680 switch (igmp_hdr(skb)->type) { ··· 741 741 */ 742 742 static bool batadv_mcast_is_report_ipv6(struct sk_buff *skb) 743 743 { 744 - if (ipv6_mc_check_mld(skb, NULL) < 0) 744 + if (ipv6_mc_check_mld(skb) < 0) 745 745 return false; 746 746 747 747 switch (icmp6_hdr(skb)->icmp6_type) {
+28 -29
net/bridge/br_multicast.c
··· 938 938 939 939 for (i = 0; i < num; i++) { 940 940 len += sizeof(*grec); 941 - if (!pskb_may_pull(skb, len)) 941 + if (!ip_mc_may_pull(skb, len)) 942 942 return -EINVAL; 943 943 944 944 grec = (void *)(skb->data + len - sizeof(*grec)); ··· 946 946 type = grec->grec_type; 947 947 948 948 len += ntohs(grec->grec_nsrcs) * 4; 949 - if (!pskb_may_pull(skb, len)) 949 + if (!ip_mc_may_pull(skb, len)) 950 950 return -EINVAL; 951 951 952 952 /* We treat this as an IGMPv2 report for now. */ ··· 985 985 struct sk_buff *skb, 986 986 u16 vid) 987 987 { 988 + unsigned int nsrcs_offset; 988 989 const unsigned char *src; 989 990 struct icmp6hdr *icmp6h; 990 991 struct mld2_grec *grec; 992 + unsigned int grec_len; 991 993 int i; 992 994 int len; 993 995 int num; 994 996 int err = 0; 995 997 996 - if (!pskb_may_pull(skb, sizeof(*icmp6h))) 998 + if (!ipv6_mc_may_pull(skb, sizeof(*icmp6h))) 997 999 return -EINVAL; 998 1000 999 1001 icmp6h = icmp6_hdr(skb); ··· 1005 1003 for (i = 0; i < num; i++) { 1006 1004 __be16 *nsrcs, _nsrcs; 1007 1005 1008 - nsrcs = skb_header_pointer(skb, 1009 - len + offsetof(struct mld2_grec, 1010 - grec_nsrcs), 1006 + nsrcs_offset = len + offsetof(struct mld2_grec, grec_nsrcs); 1007 + 1008 + if (skb_transport_offset(skb) + ipv6_transport_len(skb) < 1009 + nsrcs_offset + sizeof(_nsrcs)) 1010 + return -EINVAL; 1011 + 1012 + nsrcs = skb_header_pointer(skb, nsrcs_offset, 1011 1013 sizeof(_nsrcs), &_nsrcs); 1012 1014 if (!nsrcs) 1013 1015 return -EINVAL; 1014 1016 1015 - if (!pskb_may_pull(skb, 1016 - len + sizeof(*grec) + 1017 - sizeof(struct in6_addr) * ntohs(*nsrcs))) 1017 + grec_len = sizeof(*grec) + 1018 + sizeof(struct in6_addr) * ntohs(*nsrcs); 1019 + 1020 + if (!ipv6_mc_may_pull(skb, len + grec_len)) 1018 1021 return -EINVAL; 1019 1022 1020 1023 grec = (struct mld2_grec *)(skb->data + len); 1021 - len += sizeof(*grec) + 1022 - sizeof(struct in6_addr) * ntohs(*nsrcs); 1024 + len += grec_len; 1023 1025 1024 1026 /* We treat these as MLDv1 reports for now. */ 1025 1027 switch (grec->grec_type) { ··· 1225 1219 struct sk_buff *skb, 1226 1220 u16 vid) 1227 1221 { 1222 + unsigned int transport_len = ip_transport_len(skb); 1228 1223 const struct iphdr *iph = ip_hdr(skb); 1229 1224 struct igmphdr *ih = igmp_hdr(skb); 1230 1225 struct net_bridge_mdb_entry *mp; ··· 1235 1228 struct br_ip saddr; 1236 1229 unsigned long max_delay; 1237 1230 unsigned long now = jiffies; 1238 - unsigned int offset = skb_transport_offset(skb); 1239 1231 __be32 group; 1240 1232 1241 1233 spin_lock(&br->multicast_lock); ··· 1244 1238 1245 1239 group = ih->group; 1246 1240 1247 - if (skb->len == offset + sizeof(*ih)) { 1241 + if (transport_len == sizeof(*ih)) { 1248 1242 max_delay = ih->code * (HZ / IGMP_TIMER_SCALE); 1249 1243 1250 1244 if (!max_delay) { 1251 1245 max_delay = 10 * HZ; 1252 1246 group = 0; 1253 1247 } 1254 - } else if (skb->len >= offset + sizeof(*ih3)) { 1248 + } else if (transport_len >= sizeof(*ih3)) { 1255 1249 ih3 = igmpv3_query_hdr(skb); 1256 1250 if (ih3->nsrcs) 1257 1251 goto out; ··· 1302 1296 struct sk_buff *skb, 1303 1297 u16 vid) 1304 1298 { 1299 + unsigned int transport_len = ipv6_transport_len(skb); 1305 1300 const struct ipv6hdr *ip6h = ipv6_hdr(skb); 1306 1301 struct mld_msg *mld; 1307 1302 struct net_bridge_mdb_entry *mp; ··· 1322 1315 (port && port->state == BR_STATE_DISABLED)) 1323 1316 goto out; 1324 1317 1325 - if (skb->len == offset + sizeof(*mld)) { 1318 + if (transport_len == sizeof(*mld)) { 1326 1319 if (!pskb_may_pull(skb, offset + sizeof(*mld))) { 1327 1320 err = -EINVAL; 1328 1321 goto out; ··· 1588 1581 struct sk_buff *skb, 1589 1582 u16 vid) 1590 1583 { 1591 - struct sk_buff *skb_trimmed = NULL; 1592 1584 const unsigned char *src; 1593 1585 struct igmphdr *ih; 1594 1586 int err; 1595 1587 1596 - err = ip_mc_check_igmp(skb, &skb_trimmed); 1588 + err = ip_mc_check_igmp(skb); 1597 1589 1598 1590 if (err == -ENOMSG) { 1599 1591 if (!ipv4_is_local_multicast(ip_hdr(skb)->daddr)) { ··· 1618 1612 err = br_ip4_multicast_add_group(br, port, ih->group, vid, src); 1619 1613 break; 1620 1614 case IGMPV3_HOST_MEMBERSHIP_REPORT: 1621 - err = br_ip4_multicast_igmp3_report(br, port, skb_trimmed, vid); 1615 + err = br_ip4_multicast_igmp3_report(br, port, skb, vid); 1622 1616 break; 1623 1617 case IGMP_HOST_MEMBERSHIP_QUERY: 1624 - br_ip4_multicast_query(br, port, skb_trimmed, vid); 1618 + br_ip4_multicast_query(br, port, skb, vid); 1625 1619 break; 1626 1620 case IGMP_HOST_LEAVE_MESSAGE: 1627 1621 br_ip4_multicast_leave_group(br, port, ih->group, vid, src); 1628 1622 break; 1629 1623 } 1630 - 1631 - if (skb_trimmed && skb_trimmed != skb) 1632 - kfree_skb(skb_trimmed); 1633 1624 1634 1625 br_multicast_count(br, port, skb, BR_INPUT_SKB_CB(skb)->igmp, 1635 1626 BR_MCAST_DIR_RX); ··· 1640 1637 struct sk_buff *skb, 1641 1638 u16 vid) 1642 1639 { 1643 - struct sk_buff *skb_trimmed = NULL; 1644 1640 const unsigned char *src; 1645 1641 struct mld_msg *mld; 1646 1642 int err; 1647 1643 1648 - err = ipv6_mc_check_mld(skb, &skb_trimmed); 1644 + err = ipv6_mc_check_mld(skb); 1649 1645 1650 1646 if (err == -ENOMSG) { 1651 1647 if (!ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) ··· 1666 1664 src); 1667 1665 break; 1668 1666 case ICMPV6_MLD2_REPORT: 1669 - err = br_ip6_multicast_mld2_report(br, port, skb_trimmed, vid); 1667 + err = br_ip6_multicast_mld2_report(br, port, skb, vid); 1670 1668 break; 1671 1669 case ICMPV6_MGM_QUERY: 1672 - err = br_ip6_multicast_query(br, port, skb_trimmed, vid); 1670 + err = br_ip6_multicast_query(br, port, skb, vid); 1673 1671 break; 1674 1672 case ICMPV6_MGM_REDUCTION: 1675 1673 src = eth_hdr(skb)->h_source; 1676 1674 br_ip6_multicast_leave_group(br, port, &mld->mld_mca, vid, src); 1677 1675 break; 1678 1676 } 1679 - 1680 - if (skb_trimmed && skb_trimmed != skb) 1681 - kfree_skb(skb_trimmed); 1682 1677 1683 1678 br_multicast_count(br, port, skb, BR_INPUT_SKB_CB(skb)->igmp, 1684 1679 BR_MCAST_DIR_RX);
+4 -19
net/ipv4/igmp.c
··· 1544 1544 return skb_checksum_simple_validate(skb); 1545 1545 } 1546 1546 1547 - static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) 1547 + static int __ip_mc_check_igmp(struct sk_buff *skb) 1548 1548 1549 1549 { 1550 1550 struct sk_buff *skb_chk; ··· 1566 1566 if (ret) 1567 1567 goto err; 1568 1568 1569 - if (skb_trimmed) 1570 - *skb_trimmed = skb_chk; 1571 - /* free now unneeded clone */ 1572 - else if (skb_chk != skb) 1573 - kfree_skb(skb_chk); 1574 - 1575 1569 ret = 0; 1576 1570 1577 1571 err: 1578 - if (ret && skb_chk && skb_chk != skb) 1572 + if (skb_chk && skb_chk != skb) 1579 1573 kfree_skb(skb_chk); 1580 1574 1581 1575 return ret; ··· 1578 1584 /** 1579 1585 * ip_mc_check_igmp - checks whether this is a sane IGMP packet 1580 1586 * @skb: the skb to validate 1581 - * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) 1582 1587 * 1583 1588 * Checks whether an IPv4 packet is a valid IGMP packet. If so sets 1584 1589 * skb transport header accordingly and returns zero. ··· 1587 1594 * -ENOMSG: IP header validation succeeded but it is not an IGMP packet. 1588 1595 * -ENOMEM: A memory allocation failure happened. 1589 1596 * 1590 - * Optionally, an skb pointer might be provided via skb_trimmed (or set it 1591 - * to NULL): After parsing an IGMP packet successfully it will point to 1592 - * an skb which has its tail aligned to the IP packet end. This might 1593 - * either be the originally provided skb or a trimmed, cloned version if 1594 - * the skb frame had data beyond the IP packet. A cloned skb allows us 1595 - * to leave the original skb and its full frame unchanged (which might be 1596 - * desirable for layer 2 frame jugglers). 1597 - * 1598 1597 * Caller needs to set the skb network header and free any returned skb if it 1599 1598 * differs from the provided skb. 1600 1599 */ 1601 - int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) 1600 + int ip_mc_check_igmp(struct sk_buff *skb) 1602 1601 { 1603 1602 int ret = ip_mc_check_iphdr(skb); 1604 1603 ··· 1600 1615 if (ip_hdr(skb)->protocol != IPPROTO_IGMP) 1601 1616 return -ENOMSG; 1602 1617 1603 - return __ip_mc_check_igmp(skb, skb_trimmed); 1618 + return __ip_mc_check_igmp(skb); 1604 1619 } 1605 1620 EXPORT_SYMBOL(ip_mc_check_igmp); 1606 1621
+4 -20
net/ipv6/mcast_snoop.c
··· 136 136 return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 137 137 } 138 138 139 - static int __ipv6_mc_check_mld(struct sk_buff *skb, 140 - struct sk_buff **skb_trimmed) 139 + static int __ipv6_mc_check_mld(struct sk_buff *skb) 141 140 142 141 { 143 142 struct sk_buff *skb_chk = NULL; ··· 159 160 if (ret) 160 161 goto err; 161 162 162 - if (skb_trimmed) 163 - *skb_trimmed = skb_chk; 164 - /* free now unneeded clone */ 165 - else if (skb_chk != skb) 166 - kfree_skb(skb_chk); 167 - 168 163 ret = 0; 169 164 170 165 err: 171 - if (ret && skb_chk && skb_chk != skb) 166 + if (skb_chk && skb_chk != skb) 172 167 kfree_skb(skb_chk); 173 168 174 169 return ret; ··· 171 178 /** 172 179 * ipv6_mc_check_mld - checks whether this is a sane MLD packet 173 180 * @skb: the skb to validate 174 - * @skb_trimmed: to store an skb pointer trimmed to IPv6 packet tail (optional) 175 181 * 176 182 * Checks whether an IPv6 packet is a valid MLD packet. If so sets 177 183 * skb transport header accordingly and returns zero. ··· 180 188 * -ENOMSG: IP header validation succeeded but it is not an MLD packet. 181 189 * -ENOMEM: A memory allocation failure happened. 182 190 * 183 - * Optionally, an skb pointer might be provided via skb_trimmed (or set it 184 - * to NULL): After parsing an MLD packet successfully it will point to 185 - * an skb which has its tail aligned to the IP packet end. This might 186 - * either be the originally provided skb or a trimmed, cloned version if 187 - * the skb frame had data beyond the IP packet. A cloned skb allows us 188 - * to leave the original skb and its full frame unchanged (which might be 189 - * desirable for layer 2 frame jugglers). 190 - * 191 191 * Caller needs to set the skb network header and free any returned skb if it 192 192 * differs from the provided skb. 193 193 */ 194 - int ipv6_mc_check_mld(struct sk_buff *skb, struct sk_buff **skb_trimmed) 194 + int ipv6_mc_check_mld(struct sk_buff *skb) 195 195 { 196 196 int ret; 197 197 ··· 195 211 if (ret < 0) 196 212 return ret; 197 213 198 - return __ipv6_mc_check_mld(skb, skb_trimmed); 214 + return __ipv6_mc_check_mld(skb); 199 215 } 200 216 EXPORT_SYMBOL(ipv6_mc_check_mld);