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

netfilter: nftables: exthdr: fix 4-byte stack OOB write

If priv->len is a multiple of 4, then dst[len / 4] can write past
the destination array which leads to stack corruption.

This construct is necessary to clean the remainder of the register
in case ->len is NOT a multiple of the register size, so make it
conditional just like nft_payload.c does.

The bug was added in 4.1 cycle and then copied/inherited when
tcp/sctp and ip option support was added.

Bug reported by Zero Day Initiative project (ZDI-CAN-21950,
ZDI-CAN-21951, ZDI-CAN-21961).

Fixes: 49499c3e6e18 ("netfilter: nf_tables: switch registers to 32 bit addressing")
Fixes: 935b7f643018 ("netfilter: nft_exthdr: add TCP option matching")
Fixes: 133dc203d77d ("netfilter: nft_exthdr: Support SCTP chunks")
Fixes: dbb5281a1f84 ("netfilter: nf_tables: add support for matching IPv4 options")
Signed-off-by: Florian Westphal <fw@strlen.de>

+14 -8
+14 -8
net/netfilter/nft_exthdr.c
··· 35 35 return opt[offset + 1]; 36 36 } 37 37 38 + static int nft_skb_copy_to_reg(const struct sk_buff *skb, int offset, u32 *dest, unsigned int len) 39 + { 40 + if (len % NFT_REG32_SIZE) 41 + dest[len / NFT_REG32_SIZE] = 0; 42 + 43 + return skb_copy_bits(skb, offset, dest, len); 44 + } 45 + 38 46 static void nft_exthdr_ipv6_eval(const struct nft_expr *expr, 39 47 struct nft_regs *regs, 40 48 const struct nft_pktinfo *pkt) ··· 64 56 } 65 57 offset += priv->offset; 66 58 67 - dest[priv->len / NFT_REG32_SIZE] = 0; 68 - if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) 59 + if (nft_skb_copy_to_reg(pkt->skb, offset, dest, priv->len) < 0) 69 60 goto err; 70 61 return; 71 62 err: ··· 160 153 } 161 154 offset += priv->offset; 162 155 163 - dest[priv->len / NFT_REG32_SIZE] = 0; 164 - if (skb_copy_bits(pkt->skb, offset, dest, priv->len) < 0) 156 + if (nft_skb_copy_to_reg(pkt->skb, offset, dest, priv->len) < 0) 165 157 goto err; 166 158 return; 167 159 err: ··· 216 210 if (priv->flags & NFT_EXTHDR_F_PRESENT) { 217 211 *dest = 1; 218 212 } else { 219 - dest[priv->len / NFT_REG32_SIZE] = 0; 213 + if (priv->len % NFT_REG32_SIZE) 214 + dest[priv->len / NFT_REG32_SIZE] = 0; 220 215 memcpy(dest, opt + offset, priv->len); 221 216 } 222 217 ··· 395 388 offset + ntohs(sch->length) > pkt->skb->len) 396 389 break; 397 390 398 - dest[priv->len / NFT_REG32_SIZE] = 0; 399 - if (skb_copy_bits(pkt->skb, offset + priv->offset, 400 - dest, priv->len) < 0) 391 + if (nft_skb_copy_to_reg(pkt->skb, offset + priv->offset, 392 + dest, priv->len) < 0) 401 393 break; 402 394 return; 403 395 }