netfilter: nft_payload: restrict checksum offsets to known values

We need to prevent userspace from corrupting e.g. tcp->doff, because
many locations in conntrack and conntrack helpers rely on
nf_conntrack_in() having validated the packet headers.
nft_payload allows to alter headers later which invalidates this
assumption.

The 'Fixes' commit restricts writes to safe fields, but there is
another side channel: the checksum location.

Restrict this too.  Reported via sashiko/gemini.

Fixes: 112e447d17 ("netfilter: validate L4 headers after userspace packet writes")
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
This commit is contained in:
Florian Westphal 2026-09-05 07:01:50 +02:00 committed by Pablo Neira Ayuso
parent 48d522b65f
commit c2226f0c7f

View File

@ -20,6 +20,7 @@
#include <linux/tcp.h>
#include <linux/udp.h>
#include <net/gre.h>
#include <linux/icmp.h>
#include <linux/icmpv6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
@ -1008,11 +1009,14 @@ static bool nft_payload_validate_inet_csum_offset(const struct nft_ctx *ctx,
if (priv->csum_flags) /* makes no sense, asks for "re-update" of L4 checksum */
return false;
/* no further check here; offset can't be negative so bogus
* offsets can corrupt L4 or payload but not l3 headers.
* We already allow arbitrary l4/inner payload writes.
*/
return true;
/* Validate csum_offset is one of the supported transport header checksums */
if (priv->csum_offset == offsetof(struct tcphdr, check) ||
priv->csum_offset == offsetof(struct udphdr, check) ||
priv->csum_offset == offsetof(struct icmphdr, checksum) ||
priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum))
return true;
return false;
case NFT_PAYLOAD_INNER_HEADER:
return true;
case NFT_PAYLOAD_TUN_HEADER:
@ -1046,6 +1050,27 @@ static bool nft_payload_csum_nh_write_ok(const struct nft_payload_set *priv,
return false;
}
static bool nft_payload_csum_th_write_ok(const struct nft_payload_set *priv,
const struct nft_pktinfo *pkt)
{
if (!(pkt->flags & NFT_PKTINFO_L4PROTO))
return false;
switch (pkt->tprot) {
case IPPROTO_TCP:
return priv->csum_offset == offsetof(struct tcphdr, check);
case IPPROTO_UDP:
case IPPROTO_UDPLITE:
return priv->csum_offset == offsetof(struct udphdr, check);
case IPPROTO_ICMPV6:
return priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum);
case IPPROTO_ICMP:
return priv->csum_offset == offsetof(struct icmphdr, checksum);
}
return false;
}
static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
const struct nft_payload_set *priv)
{
@ -1055,9 +1080,10 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
case NFT_PAYLOAD_NETWORK_HEADER:
return nft_payload_csum_nh_write_ok(priv, pkt);
case NFT_PAYLOAD_TRANSPORT_HEADER:
return nft_payload_csum_th_write_ok(priv, pkt);
case NFT_PAYLOAD_INNER_HEADER:
/* neither offsets are validated, offsets cannot be
* negative so real l3 headers cannot be mangled.
/* offset is not validated, offset cannot be
* negative so real l3/l4 headers cannot be mangled.
*/
return true;
case NFT_PAYLOAD_TUN_HEADER: