From: Michal Schmidt <mschmidt@redhat.com> Date: Fri, 3 Sep 2010 19:57:46 -0400 Subject: [net] vlan/bridge: fix skb_pull_rcsum fatal exception Message-id: <20100903195744.7750.82062.stgit@localhost6.localdomain6> Patchwork-id: 28127 O-Subject: [RHEL5.6 BZ556476 PATCH 1/6] vlan/bridge: Fix "skb_pull_rcsum - Fatal exception in interrupt" Bugzilla: 556476 RH-Acked-by: David S. Miller <davem@redhat.com> RH-Acked-by: Thomas Graf <tgraf@redhat.com> From: Evgeniy Polyakov <johnpol@2ka.mipt.ru> commit e7c243c925f6d9dcb898504ff24d6650b5cbb3b1 upstream. I tried to preserve bridging code as it was before, but logic is quite strange - I think we should free skb on error, since it is already unshared and thus will just leak. Herbert Xu states: > + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) > + goto out; If this happens it'll be a double-free on skb since we'll return NF_DROP which makes the caller free it too. We could return NF_STOLEN to prevent that but I'm not sure whether that's correct netfilter semantics. Patrick, could you please make a call on this? Patrick McHardy states: NF_STOLEN should work fine here. Signed-off-by: Evgeniy Polyakov <johnpol@2ka.mipt.ru> Signed-off-by: David S. Miller <davem@davemloft.net> [bwh: Backport to RHEL 5] diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c index 158a2ae..366816d 100644 --- a/net/8021q/vlan_dev.c +++ b/net/8021q/vlan_dev.c @@ -117,12 +117,22 @@ int vlan_skb_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type* ptype, struct net_device *orig_dev) { unsigned char *rawp = NULL; - struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb->data); + struct vlan_hdr *vhdr; unsigned short vid; struct net_device_stats *stats; unsigned short vlan_TCI; __be16 proto; + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) + return -1; + + if (unlikely(!pskb_may_pull(skb, VLAN_HLEN))) { + kfree_skb(skb); + return -1; + } + + vhdr = (struct vlan_hdr *)(skb->data); + /* vlan_TCI = ntohs(get_unaligned(&vhdr->h_vlan_TCI)); */ vlan_TCI = ntohs(vhdr->h_vlan_TCI); diff --git a/net/bridge/br_netfilter.c b/net/bridge/br_netfilter.c index 05b3de8..bad0e08 100644 --- a/net/bridge/br_netfilter.c +++ b/net/bridge/br_netfilter.c @@ -446,13 +446,18 @@ static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb, __u32 len; struct sk_buff *skb = *pskb; + if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) + return NF_STOLEN; + + if (skb->protocol == htons(ETH_P_8021Q) && + unlikely(!pskb_may_pull(skb, VLAN_HLEN))) + goto out; + if (skb->protocol == htons(ETH_P_IPV6) || IS_VLAN_IPV6(skb)) { #ifdef CONFIG_SYSCTL if (!brnf_call_ip6tables) return NF_ACCEPT; #endif - if ((skb = skb_share_check(*pskb, GFP_ATOMIC)) == NULL) - goto out; if (skb->protocol == htons(ETH_P_8021Q)) { skb_pull_rcsum(skb, VLAN_HLEN); @@ -468,9 +473,6 @@ static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb, if (skb->protocol != htons(ETH_P_IP) && !IS_VLAN_IP(skb)) return NF_ACCEPT; - if ((skb = skb_share_check(*pskb, GFP_ATOMIC)) == NULL) - goto out; - if (skb->protocol == htons(ETH_P_8021Q)) { skb_pull_rcsum(skb, VLAN_HLEN); skb->nh.raw += VLAN_HLEN;