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

netfilter: nft_inner: incorrect percpu area handling under softirq

Softirq can interrupt ongoing packet from process context that is
walking over the percpu area that contains inner header offsets.

Disable bh and perform three checks before restoring the percpu inner
header offsets to validate that the percpu area is valid for this
skbuff:

1) If the NFT_PKTINFO_INNER_FULL flag is set on, then this skbuff
has already been parsed before for inner header fetching to
register.

2) Validate that the percpu area refers to this skbuff using the
skbuff pointer as a cookie. If there is a cookie mismatch, then
this skbuff needs to be parsed again.

3) Finally, validate if the percpu area refers to this tunnel type.

Only after these three checks the percpu area is restored to a on-stack
copy and bh is enabled again.

After inner header fetching, the on-stack copy is stored back to the
percpu area.

Fixes: 3a07327d10a0 ("netfilter: nft_inner: support for inner tunnel header matching")
Reported-by: syzbot+84d0441b9860f0d63285@syzkaller.appspotmail.com
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>

+47 -13
+1
include/net/netfilter/nf_tables_core.h
··· 161 161 }; 162 162 163 163 struct nft_inner_tun_ctx { 164 + unsigned long cookie; 164 165 u16 type; 165 166 u16 inner_tunoff; 166 167 u16 inner_lloff;
+46 -13
net/netfilter/nft_inner.c
··· 210 210 struct nft_pktinfo *pkt, 211 211 struct nft_inner_tun_ctx *tun_ctx) 212 212 { 213 - struct nft_inner_tun_ctx ctx = {}; 214 213 u32 off = pkt->inneroff; 215 214 216 215 if (priv->flags & NFT_INNER_HDRSIZE && 217 - nft_inner_parse_tunhdr(priv, pkt, &ctx, &off) < 0) 216 + nft_inner_parse_tunhdr(priv, pkt, tun_ctx, &off) < 0) 218 217 return -1; 219 218 220 219 if (priv->flags & (NFT_INNER_LL | NFT_INNER_NH)) { 221 - if (nft_inner_parse_l2l3(priv, pkt, &ctx, off) < 0) 220 + if (nft_inner_parse_l2l3(priv, pkt, tun_ctx, off) < 0) 222 221 return -1; 223 222 } else if (priv->flags & NFT_INNER_TH) { 224 - ctx.inner_thoff = off; 225 - ctx.flags |= NFT_PAYLOAD_CTX_INNER_TH; 223 + tun_ctx->inner_thoff = off; 224 + tun_ctx->flags |= NFT_PAYLOAD_CTX_INNER_TH; 226 225 } 227 226 228 - *tun_ctx = ctx; 229 227 tun_ctx->type = priv->type; 228 + tun_ctx->cookie = (unsigned long)pkt->skb; 230 229 pkt->flags |= NFT_PKTINFO_INNER_FULL; 231 230 232 231 return 0; 233 232 } 234 233 235 - static bool nft_inner_parse_needed(const struct nft_inner *priv, 236 - const struct nft_pktinfo *pkt, 234 + static bool nft_inner_restore_tun_ctx(const struct nft_pktinfo *pkt, 235 + struct nft_inner_tun_ctx *tun_ctx) 236 + { 237 + struct nft_inner_tun_ctx *this_cpu_tun_ctx; 238 + 239 + local_bh_disable(); 240 + this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx); 241 + if (this_cpu_tun_ctx->cookie != (unsigned long)pkt->skb) { 242 + local_bh_enable(); 243 + return false; 244 + } 245 + *tun_ctx = *this_cpu_tun_ctx; 246 + local_bh_enable(); 247 + 248 + return true; 249 + } 250 + 251 + static void nft_inner_save_tun_ctx(const struct nft_pktinfo *pkt, 237 252 const struct nft_inner_tun_ctx *tun_ctx) 238 253 { 254 + struct nft_inner_tun_ctx *this_cpu_tun_ctx; 255 + 256 + local_bh_disable(); 257 + this_cpu_tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx); 258 + if (this_cpu_tun_ctx->cookie != tun_ctx->cookie) 259 + *this_cpu_tun_ctx = *tun_ctx; 260 + local_bh_enable(); 261 + } 262 + 263 + static bool nft_inner_parse_needed(const struct nft_inner *priv, 264 + const struct nft_pktinfo *pkt, 265 + struct nft_inner_tun_ctx *tun_ctx) 266 + { 239 267 if (!(pkt->flags & NFT_PKTINFO_INNER_FULL)) 268 + return true; 269 + 270 + if (!nft_inner_restore_tun_ctx(pkt, tun_ctx)) 240 271 return true; 241 272 242 273 if (priv->type != tun_ctx->type) ··· 279 248 static void nft_inner_eval(const struct nft_expr *expr, struct nft_regs *regs, 280 249 const struct nft_pktinfo *pkt) 281 250 { 282 - struct nft_inner_tun_ctx *tun_ctx = this_cpu_ptr(&nft_pcpu_tun_ctx); 283 251 const struct nft_inner *priv = nft_expr_priv(expr); 252 + struct nft_inner_tun_ctx tun_ctx = {}; 284 253 285 254 if (nft_payload_inner_offset(pkt) < 0) 286 255 goto err; 287 256 288 - if (nft_inner_parse_needed(priv, pkt, tun_ctx) && 289 - nft_inner_parse(priv, (struct nft_pktinfo *)pkt, tun_ctx) < 0) 257 + if (nft_inner_parse_needed(priv, pkt, &tun_ctx) && 258 + nft_inner_parse(priv, (struct nft_pktinfo *)pkt, &tun_ctx) < 0) 290 259 goto err; 291 260 292 261 switch (priv->expr_type) { 293 262 case NFT_INNER_EXPR_PAYLOAD: 294 - nft_payload_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, tun_ctx); 263 + nft_payload_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, &tun_ctx); 295 264 break; 296 265 case NFT_INNER_EXPR_META: 297 - nft_meta_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, tun_ctx); 266 + nft_meta_inner_eval((struct nft_expr *)&priv->expr, regs, pkt, &tun_ctx); 298 267 break; 299 268 default: 300 269 WARN_ON_ONCE(1); 301 270 goto err; 302 271 } 272 + nft_inner_save_tun_ctx(pkt, &tun_ctx); 273 + 303 274 return; 304 275 err: 305 276 regs->verdict.code = NFT_BREAK;