* [net] AF_PACKET PACKET_VNET_HDR CHECKSUM_PARTIAL packets bypass ct invalid classification
@ 2026-05-19 13:13 Federico Brasili
0 siblings, 0 replies; 3+ messages in thread
From: Federico Brasili @ 2026-05-19 13:13 UTC (permalink / raw)
To: netdev
Cc: netfilter-devel, willemdebruijn.kernel@gmail.com, davem,
edumazet@google.com, kuba, pabeni@redhat.com, fw@strlen.de,
pablo@netfilter.org
Hello,
I would like to ask for feedback on a possible checksum/conntrack
inconsistency in the AF_PACKET PACKET_VNET_HDR transmit path.
A locally injected IPv4/UDP packet with an invalid raw UDP checksum is
classified as ct state invalid when sent as a normal AF_PACKET raw
frame. However, an otherwise equivalent packet sent through AF_PACKET
with PACKET_VNET_HDR and VIRTIO_NET_HDR_F_NEEDS_CSUM is not classified
as invalid and is delivered to a UDP socket, even though packet
sockets still observe the UDP checksum field unchanged and report
CSUMNOTREADY.
Minimal behavior observed:
RAW_BAD
AF_PACKET raw frame
UDP checksum field: 0x1111
nft:
ct state invalid counter packets 1 drop
udp dport 12345 counter packets 0 accept
UDP socket:
no packet received
VNET_BAD
AF_PACKET + PACKET_VNET_HDR
VIRTIO_NET_HDR_F_NEEDS_CSUM
csum_start = 34
csum_offset = 6
UDP checksum field: 0x1111
packet socket:
PACKET_AUXDATA reports CSUMNOTREADY
UDP header still contains checksum 0x1111
nft:
ct state invalid counter packets 0 drop
udp dport 12345 counter packets 1 accept
UDP socket:
packet received
A trace of the VNET case shows the packet being converted to
CHECKSUM_PARTIAL and reaching conntrack/UDP in that state:
skb_partial_csum_set(... arg_start=34 arg_off=6) = 1
XMIT ip_summed=3 csum_start=36 csum_offset=6
NF_CT_UDP ip_summed=3 csum_start=36 csum_offset=6
UDP_RCV ip_summed=3 csum_start=36 csum_offset=6
UDP_QUEUE ip_summed=3 csum_start=36 csum_offset=6
The relevant path appears to be:
net/packet/af_packet.c
packet_snd()
tpacket_snd()
__packet_snd_vnet_parse()
virtio_net_hdr_to_skb()
include/linux/virtio_net.h
__virtio_net_hdr_to_skb()
skb_partial_csum_set()
The same behavior was also reproduced through PACKET_TX_RING + PACKET_VNET_HDR.
An explicit nftables rule such as udp dport 12345 drop still works
correctly, so this is not a general firewall bypass. The observed
difference is specifically around checksum-invalid classification: raw
invalid packets are treated as ct state invalid, while
PACKET_VNET_HDR/NEEDS_CSUM packets with the same invalid raw checksum
are not.
My question is whether this is considered intended behavior for
locally injected CHECKSUM_PARTIAL skbs, or whether AF_PACKET should
reject or normalize this case before the packet reaches conntrack/UDP.
I can provide the minimal reproducer and full logs privately if useful.
Tested on:
Linux 6.19.14+kali-amd64 x86_64
Thanks,
Federico
^ permalink raw reply [flat|nested] 3+ messages in thread
* [net] AF_PACKET PACKET_VNET_HDR CHECKSUM_PARTIAL packets bypass ct invalid classification
@ 2026-05-19 13:25 Federico Brasili
2026-05-20 1:29 ` Willem de Bruijn
0 siblings, 1 reply; 3+ messages in thread
From: Federico Brasili @ 2026-05-19 13:25 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Pablo Neira Ayuso, Florian Westphal, netfilter-devel
Hello,
I would like to ask for feedback on a possible checksum/conntrack inconsistency in the AF_PACKET PACKET_VNET_HDR transmit path.
A locally injected IPv4/UDP packet with an invalid raw UDP checksum is classified as ct state invalid when sent as a normal AF_PACKET raw frame. However, an otherwise equivalent packet sent through AF_PACKET with PACKET_VNET_HDR and VIRTIO_NET_HDR_F_NEEDS_CSUM is not classified as invalid and is delivered to a UDP socket, even though packet sockets still observe the UDP checksum field unchanged and report CSUMNOTREADY.
Minimal behavior observed:
1. RAW_BAD
AF_PACKET raw frame
UDP checksum field: 0x1111
nft:
ct state invalid counter packets 1 drop
udp dport 12345 counter packets 0 accept
UDP socket:
no packet received
2. VNET_BAD
AF_PACKET + PACKET_VNET_HDR
VIRTIO_NET_HDR_F_NEEDS_CSUM
csum_start = 34
csum_offset = 6
UDP checksum field: 0x1111
packet socket:
PACKET_AUXDATA reports CSUMNOTREADY
UDP header still contains checksum 0x1111
nft:
ct state invalid counter packets 0 drop
udp dport 12345 counter packets 1 accept
UDP socket:
packet received
A trace of the VNET case shows the packet being converted to CHECKSUM_PARTIAL and reaching conntrack/UDP in that state:
skb_partial_csum_set(... arg_start=34 arg_off=6) = 1
XMIT ip_summed=3 csum_start=36 csum_offset=6
NF_CT_UDP ip_summed=3 csum_start=36 csum_offset=6
UDP_RCV ip_summed=3 csum_start=36 csum_offset=6
UDP_QUEUE ip_summed=3 csum_start=36 csum_offset=6
The relevant path appears to be:
net/packet/af_packet.c
packet_snd()
tpacket_snd()
__packet_snd_vnet_parse()
virtio_net_hdr_to_skb()
include/linux/virtio_net.h
__virtio_net_hdr_to_skb()
skb_partial_csum_set()
The same behavior was also reproduced through PACKET_TX_RING + PACKET_VNET_HDR.
An explicit nftables rule such as udp dport 12345 drop still works correctly, so this is not a general firewall bypass. The observed difference is specifically around checksum-invalid classification: raw invalid packets are treated as ct state invalid, while PACKET_VNET_HDR/NEEDS_CSUM packets with the same invalid raw checksum are not.
My question is whether this is considered intended behavior for locally injected CHECKSUM_PARTIAL skbs, or whether AF_PACKET should reject or normalize this case before the packet reaches conntrack/UDP.
I can provide the minimal reproducer and full logs privately if useful.
Tested on:
Linux 6.19.14+kali-amd64 x86_64
Thanks,
Federico
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [net] AF_PACKET PACKET_VNET_HDR CHECKSUM_PARTIAL packets bypass ct invalid classification
2026-05-19 13:25 [net] AF_PACKET PACKET_VNET_HDR CHECKSUM_PARTIAL packets bypass ct invalid classification Federico Brasili
@ 2026-05-20 1:29 ` Willem de Bruijn
0 siblings, 0 replies; 3+ messages in thread
From: Willem de Bruijn @ 2026-05-20 1:29 UTC (permalink / raw)
To: Federico Brasili, netdev
Cc: Willem de Bruijn, David S . Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Pablo Neira Ayuso, Florian Westphal, netfilter-devel
Federico Brasili wrote:
> Hello,
>
> I would like to ask for feedback on a possible checksum/conntrack inconsistency in the AF_PACKET PACKET_VNET_HDR transmit path.
>
> A locally injected IPv4/UDP packet with an invalid raw UDP checksum is classified as ct state invalid when sent as a normal AF_PACKET raw frame. However, an otherwise equivalent packet sent through AF_PACKET with PACKET_VNET_HDR and VIRTIO_NET_HDR_F_NEEDS_CSUM is not classified as invalid and is delivered to a UDP socket, even though packet sockets still observe the UDP checksum field unchanged and report CSUMNOTREADY.
>
> Minimal behavior observed:
>
> 1. RAW_BAD
>
> AF_PACKET raw frame
> UDP checksum field: 0x1111
>
> nft:
> ct state invalid counter packets 1 drop
> udp dport 12345 counter packets 0 accept
>
> UDP socket:
> no packet received
>
> 2. VNET_BAD
>
> AF_PACKET + PACKET_VNET_HDR
> VIRTIO_NET_HDR_F_NEEDS_CSUM
> csum_start = 34
> csum_offset = 6
> UDP checksum field: 0x1111
>
> packet socket:
> PACKET_AUXDATA reports CSUMNOTREADY
> UDP header still contains checksum 0x1111
>
> nft:
> ct state invalid counter packets 0 drop
> udp dport 12345 counter packets 1 accept
>
> UDP socket:
> packet received
>
> A trace of the VNET case shows the packet being converted to CHECKSUM_PARTIAL and reaching conntrack/UDP in that state:
>
> skb_partial_csum_set(... arg_start=34 arg_off=6) = 1
> XMIT ip_summed=3 csum_start=36 csum_offset=6
> NF_CT_UDP ip_summed=3 csum_start=36 csum_offset=6
> UDP_RCV ip_summed=3 csum_start=36 csum_offset=6
> UDP_QUEUE ip_summed=3 csum_start=36 csum_offset=6
>
> The relevant path appears to be:
>
> net/packet/af_packet.c
> packet_snd()
> tpacket_snd()
> __packet_snd_vnet_parse()
> virtio_net_hdr_to_skb()
>
> include/linux/virtio_net.h
> __virtio_net_hdr_to_skb()
> skb_partial_csum_set()
>
> The same behavior was also reproduced through PACKET_TX_RING + PACKET_VNET_HDR.
>
> An explicit nftables rule such as udp dport 12345 drop still works correctly, so this is not a general firewall bypass. The observed difference is specifically around checksum-invalid classification: raw invalid packets are treated as ct state invalid, while PACKET_VNET_HDR/NEEDS_CSUM packets with the same invalid raw checksum are not.
>
> My question is whether this is considered intended behavior for locally injected CHECKSUM_PARTIAL skbs, or whether AF_PACKET should reject or normalize this case before the packet reaches conntrack/UDP.
This is expected.
The VIRTIO_NET_HDR_F_NEEDS_CSUM flag on transmit indicates that a
checksum hardware offload (CHECKSUM_PARTIAL) is to be programmed.
The sender will include checksum start and offset instructions.
Handling of CHECKSUM_PARTIAL skbuffs inside the kernel is described at
the top of skbuff.h. Note the section on receive processing, the point
about "are considered verified":
* - %CHECKSUM_PARTIAL
*
* A checksum is set up to be offloaded to a device as described in the
* output description for CHECKSUM_PARTIAL. This may occur on a packet
* received directly from another Linux OS, e.g., a virtualized Linux kernel
* on the same host, or it may be set in the input path in GRO or remote
* checksum offload. For the purposes of checksum verification, the checksum
* referred to by skb->csum_start + skb->csum_offset and any preceding
* checksums in the packet are considered verified. Any checksums in the
* packet that are after the checksum being offloaded are not considered to
* be verified.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-05-20 1:29 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-19 13:25 [net] AF_PACKET PACKET_VNET_HDR CHECKSUM_PARTIAL packets bypass ct invalid classification Federico Brasili
2026-05-20 1:29 ` Willem de Bruijn
-- strict thread matches above, loose matches on Subject: below --
2026-05-19 13:13 Federico Brasili
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox