[XFRM]: Fix SNAT-related crash in xfrm4_output_finish

When a packet matching an IPsec policy is SNATed so it doesn't match any
policy anymore it looses its xfrm bundle, which makes xfrm4_output_finish
crash because of a NULL pointer dereference.

This patch directs these packets to the original output path instead. Since
the packets have already passed the POST_ROUTING hook, but need to start at
the beginning of the original output path which includes another
POST_ROUTING invocation, a flag is added to the IPCB to indicate that the
packet was rerouted and doesn't need to pass the POST_ROUTING hook again.

Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by Patrick McHardy and committed by David S. Miller 48d5cad8 5ecfbae0

+40 -16
+15 -4
include/linux/netfilter.h
··· 184 184 struct sk_buff **pskb, 185 185 struct net_device *indev, 186 186 struct net_device *outdev, 187 - int (*okfn)(struct sk_buff *), int thresh) 187 + int (*okfn)(struct sk_buff *), int thresh, 188 + int cond) 188 189 { 190 + if (!cond) 191 + return 1; 189 192 #ifndef CONFIG_NETFILTER_DEBUG 190 193 if (list_empty(&nf_hooks[pf][hook])) 191 194 return 1; ··· 200 197 struct net_device *indev, struct net_device *outdev, 201 198 int (*okfn)(struct sk_buff *)) 202 199 { 203 - return nf_hook_thresh(pf, hook, pskb, indev, outdev, okfn, INT_MIN); 200 + return nf_hook_thresh(pf, hook, pskb, indev, outdev, okfn, INT_MIN, 1); 204 201 } 205 202 206 203 /* Activate hook; either okfn or kfree_skb called, unless a hook ··· 227 224 228 225 #define NF_HOOK_THRESH(pf, hook, skb, indev, outdev, okfn, thresh) \ 229 226 ({int __ret; \ 230 - if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh)) == 1)\ 227 + if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, thresh, 1)) == 1)\ 228 + __ret = (okfn)(skb); \ 229 + __ret;}) 230 + 231 + #define NF_HOOK_COND(pf, hook, skb, indev, outdev, okfn, cond) \ 232 + ({int __ret; \ 233 + if ((__ret=nf_hook_thresh(pf, hook, &(skb), indev, outdev, okfn, INT_MIN, cond)) == 1)\ 231 234 __ret = (okfn)(skb); \ 232 235 __ret;}) 233 236 ··· 304 295 305 296 #else /* !CONFIG_NETFILTER */ 306 297 #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb) 298 + #define NF_HOOK_COND(pf, hook, skb, indev, outdev, okfn, cond) (okfn)(skb) 307 299 static inline int nf_hook_thresh(int pf, unsigned int hook, 308 300 struct sk_buff **pskb, 309 301 struct net_device *indev, 310 302 struct net_device *outdev, 311 - int (*okfn)(struct sk_buff *), int thresh) 303 + int (*okfn)(struct sk_buff *), int thresh, 304 + int cond) 312 305 { 313 306 return okfn(*pskb); 314 307 }
+1
include/net/ip.h
··· 41 41 #define IPSKB_XFRM_TUNNEL_SIZE 2 42 42 #define IPSKB_XFRM_TRANSFORMED 4 43 43 #define IPSKB_FRAG_COMPLETE 8 44 + #define IPSKB_REROUTED 16 44 45 }; 45 46 46 47 struct ipcm_cookie
-1
include/net/xfrm.h
··· 866 866 extern int xfrm_init_state(struct xfrm_state *x); 867 867 extern int xfrm4_rcv(struct sk_buff *skb); 868 868 extern int xfrm4_output(struct sk_buff *skb); 869 - extern int xfrm4_output_finish(struct sk_buff *skb); 870 869 extern int xfrm4_tunnel_register(struct xfrm_tunnel *handler); 871 870 extern int xfrm4_tunnel_deregister(struct xfrm_tunnel *handler); 872 871 extern int xfrm6_rcv_spi(struct sk_buff **pskb, u32 spi);
+2 -1
net/ipv4/ip_gre.c
··· 830 830 skb->h.raw = skb->nh.raw; 831 831 skb->nh.raw = skb_push(skb, gre_hlen); 832 832 memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); 833 - IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE|IPSKB_XFRM_TRANSFORMED); 833 + IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED | 834 + IPSKB_REROUTED); 834 835 dst_release(skb->dst); 835 836 skb->dst = &rt->u.dst; 836 837
+10 -6
net/ipv4/ip_output.c
··· 207 207 { 208 208 #if defined(CONFIG_NETFILTER) && defined(CONFIG_XFRM) 209 209 /* Policy lookup after SNAT yielded a new policy */ 210 - if (skb->dst->xfrm != NULL) 211 - return xfrm4_output_finish(skb); 210 + if (skb->dst->xfrm != NULL) { 211 + IPCB(skb)->flags |= IPSKB_REROUTED; 212 + return dst_output(skb); 213 + } 212 214 #endif 213 215 if (skb->len > dst_mtu(skb->dst) && 214 216 !(skb_shinfo(skb)->ufo_size || skb_shinfo(skb)->tso_size)) ··· 273 271 newskb->dev, ip_dev_loopback_xmit); 274 272 } 275 273 276 - return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dev, 277 - ip_finish_output); 274 + return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dev, 275 + ip_finish_output, 276 + !(IPCB(skb)->flags & IPSKB_REROUTED)); 278 277 } 279 278 280 279 int ip_output(struct sk_buff *skb) ··· 287 284 skb->dev = dev; 288 285 skb->protocol = htons(ETH_P_IP); 289 286 290 - return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev, 291 - ip_finish_output); 287 + return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev, 288 + ip_finish_output, 289 + !(IPCB(skb)->flags & IPSKB_REROUTED)); 292 290 } 293 291 294 292 int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
+2 -1
net/ipv4/ipip.c
··· 622 622 skb->h.raw = skb->nh.raw; 623 623 skb->nh.raw = skb_push(skb, sizeof(struct iphdr)); 624 624 memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt)); 625 - IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE|IPSKB_XFRM_TRANSFORMED); 625 + IPCB(skb)->flags &= ~(IPSKB_XFRM_TUNNEL_SIZE | IPSKB_XFRM_TRANSFORMED | 626 + IPSKB_REROUTED); 626 627 dst_release(skb->dst); 627 628 skb->dst = &rt->u.dst; 628 629
+10 -3
net/ipv4/xfrm4_output.c
··· 152 152 goto out_exit; 153 153 } 154 154 155 - int xfrm4_output_finish(struct sk_buff *skb) 155 + static int xfrm4_output_finish(struct sk_buff *skb) 156 156 { 157 157 int err; 158 158 159 + #ifdef CONFIG_NETFILTER 160 + if (!skb->dst->xfrm) { 161 + IPCB(skb)->flags |= IPSKB_REROUTED; 162 + return dst_output(skb); 163 + } 164 + #endif 159 165 while (likely((err = xfrm4_output_one(skb)) == 0)) { 160 166 nf_reset(skb); 161 167 ··· 184 178 185 179 int xfrm4_output(struct sk_buff *skb) 186 180 { 187 - return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev, 188 - xfrm4_output_finish); 181 + return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, skb->dst->dev, 182 + xfrm4_output_finish, 183 + !(IPCB(skb)->flags & IPSKB_REROUTED)); 189 184 }