mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Todd Freed <toddfreed@meta.com>
To: <netfilter-devel@vger.kernel.org>, <toddfreed@meta.com>
Cc: "Pablo Neira Ayuso" <pablo@netfilter.org>,
	"Florian Westphal" <fw@strlen.de>, "Phil Sutter" <phil@nwl.cc>,
	"Nikolay Aleksandrov" <razor@blackwall.org>,
	"Ido Schimmel" <idosch@nvidia.com>,
	"David S. Miller" <davem@davemloft.net>,
	"Eric Dumazet" <edumazet@kernel.org>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Simon Horman" <horms@kernel.org>,
	"Boris Pismenny" <borisp@nvidia.com>,
	"Sabrina Dubroca" <sd@queasysnail.net>,
	"John Fastabend" <john.fastabend@gmail.com>,
	"Daniel Zahka" <daniel.zahka@gmail.com>,
	"Willem de Bruijn" <willemdebruijn.kernel@gmail.com>,
	"Björn Töpel" <bjorn@kernel.org>,
	coreteam@netfilter.org, bridge@lists.linux.dev,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH nf-next v1 2/2] netfilter: reject: don't reply to decrypted skbs
Date: Tue, 10 Nov 2026 16:31:11 -0800	[thread overview]
Message-ID: <20261111003111.3826942-3-toddfreed@meta.com> (raw)
In-Reply-To: <20261111003111.3826942-1-toddfreed@meta.com>

on receive, skb->decrypted means that the device decrypted the skb (tls
device offload, psp). for a decrypted skb, any part of the packet which
had been encrypted should not be sent back onto the network in
cleartext.

"ipv4: icmp: don't quote decrypted payload in icmp errors" and
"ipv6: icmpv6: don't quote decrypted payload in icmpv6 errors" (net)
make the ip stack send no icmp error about a received decrypted skb.
tls and psp skbs can't be told apart reliably, since
skb_scrub_packet() drops SKB_EXT_PSP and keeps skb->decrypted, so the
rule covers both.

the reject code builds its own replies, without icmp_send() and
icmpv6_send():

 - nf_reject_skb_v4_unreach() and nf_reject_skb_v6_unreach(), used by
   the reject expression in netdev and bridge chains, quote up to 536
   and 1220 bytes of the packet.
 - the tcp resets of nf_reject_skb_v4_tcp_reset(),
   nf_reject_skb_v6_tcp_reset(), nf_send_reset() and nf_send_reset6()
   carry the ports and sequence numbers of the packet's tcp header,
   which are encrypted with psp.

apply the same rule here:

 - nf_reject_skb_v4_tcp_reset(), nf_reject_skb_v4_unreach() and their
   ipv6 versions only get received skbs: the bridge and netdev reject
   expressions are limited to the prerouting, input and ingress hooks.
   don't send a reply for a decrypted skb.
 - nf_send_reset() and nf_send_reset6() also get locally originated
   skbs, from a reject rule in the output chain. don't send a reset
   about a received decrypted skb. for a locally originated, decrypted
   skb, only send the reset if it is routed to a local address
   (RTCF_LOCAL, RTF_LOCAL), as icmp_send() and icmpv6_send() do.

a reject rule then drops a received decrypted packet without a reply, as
it does a packet it can't reply to, and conntrack isn't moved to closing
either, as with a drop rule. nf_send_unreach() and nf_send_unreach6()
use icmp_send() and icmpv6_send(), and get the rule from the patches in
net.

Signed-off-by: Todd Freed <toddfreed@meta.com>
---
 net/ipv4/netfilter/nf_reject_ipv4.c | 26 ++++++++++++++++++++++++++
 net/ipv6/netfilter/nf_reject_ipv6.c | 28 ++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)

diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
index 7cb33af671e2..1ad5ada45536 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -59,6 +59,12 @@ struct sk_buff *nf_reject_skb_v4_tcp_reset(struct net *net,
 	if (!nf_reject_iphdr_validate(oldskb))
 		return NULL;
 
+	/* Only received skbs get here (bridge, netdev ingress): see
+	 * nf_send_reset()
+	 */
+	if (unlikely(skb_is_decrypted(oldskb)))
+		return NULL;
+
 	oth = nf_reject_ip_tcphdr_get(oldskb, &_oth, hook);
 	if (!oth)
 		return NULL;
@@ -118,6 +124,12 @@ struct sk_buff *nf_reject_skb_v4_unreach(struct net *net,
 	if (!nf_reject_iphdr_validate(oldskb))
 		return NULL;
 
+	/* Only received skbs get here (bridge, netdev ingress): see
+	 * nf_send_reset()
+	 */
+	if (unlikely(skb_is_decrypted(oldskb)))
+		return NULL;
+
 	/* IP header checks: fragment. */
 	if (ip_hdr(oldskb)->frag_off & htons(IP_OFFSET))
 		return NULL;
@@ -287,6 +299,16 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	if (skb_rtable(oldskb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
 		return;
 
+	/* Don't send the decrypted part of an skb onto the network. A
+	 * received skb was decrypted by the device with crypto offload, which
+	 * in some cases encrypts the transport header. The different offloads
+	 * can't be reliably distinguished, so don't reply at all. A locally
+	 * originated skb to be encrypted gets a reply if the reply is routed to
+	 * a local address.
+	 */
+	if (unlikely(skb_is_decrypted(oldskb) && oldskb->skb_iif))
+		return;
+
 	nskb = alloc_skb(sizeof(struct iphdr) + sizeof(struct tcphdr) +
 			 LL_MAX_HEADER, GFP_ATOMIC);
 	if (!nskb)
@@ -304,6 +326,10 @@ void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	if (ip_route_me_harder(net, sk, nskb, RTN_UNSPEC))
 		goto free_nskb;
 
+	if (unlikely(skb_is_decrypted(oldskb)) &&
+	    !(skb_rtable(nskb)->rt_flags & RTCF_LOCAL))
+		goto free_nskb;
+
 	/* "Never happens" */
 	if (nskb->len > dst4_mtu(skb_dst(nskb)))
 		goto free_nskb;
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
index 5e08d8529cc8..de815538ebf1 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -84,6 +84,12 @@ struct sk_buff *nf_reject_skb_v6_tcp_reset(struct net *net,
 	if (!nf_reject_ip6hdr_validate(oldskb))
 		return NULL;
 
+	/* Only received skbs get here (bridge, netdev ingress): see
+	 * nf_send_reset6()
+	 */
+	if (unlikely(skb_is_decrypted(oldskb)))
+		return NULL;
+
 	oth = nf_reject_ip6_tcphdr_get(oldskb, &_oth, &otcplen, hook);
 	if (!oth)
 		return NULL;
@@ -144,6 +150,12 @@ struct sk_buff *nf_reject_skb_v6_unreach(struct net *net,
 	if (!nf_reject_ip6hdr_validate(oldskb))
 		return NULL;
 
+	/* Only received skbs get here (bridge, netdev ingress): see
+	 * nf_send_reset6()
+	 */
+	if (unlikely(skb_is_decrypted(oldskb)))
+		return NULL;
+
 	/* Don't reply to ICMPV6_DEST_UNREACH with ICMPV6_DEST_UNREACH */
 	if (nf_skb_is_icmp6_unreach(oldskb))
 		return NULL;
@@ -331,6 +343,16 @@ void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	if (!otcph)
 		return;
 
+	/* Don't send the decrypted part of an skb onto the network. A
+	 * received skb was decrypted by the device with crypto offload, which
+	 * in some cases encrypts the transport header. The different offloads
+	 * can't be reliably distinguished, so don't reply at all. A locally
+	 * originated skb to be encrypted gets a reply if the reply is routed to
+	 * a local address.
+	 */
+	if (unlikely(skb_is_decrypted(oldskb) && oldskb->skb_iif))
+		return;
+
 	memset(&fl6, 0, sizeof(fl6));
 	fl6.flowi6_proto = IPPROTO_TCP;
 	fl6.saddr = oip6h->daddr;
@@ -359,6 +381,12 @@ void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb,
 	if (IS_ERR(dst))
 		return;
 
+	if (unlikely(skb_is_decrypted(oldskb)) &&
+	    !(dst_rt6_info(dst)->rt6i_flags & RTF_LOCAL)) {
+		dst_release(dst);
+		return;
+	}
+
 	nskb = alloc_skb(LL_MAX_HEADER + sizeof(struct ipv6hdr) +
 			 sizeof(struct tcphdr) + dst->trailer_len,
 			 GFP_ATOMIC);
-- 
2.53.0-Meta


  parent reply	other threads:[~2026-11-11  0:32 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-11  0:31 [PATCH nf-next 0/2] netfilter: don't send decrypted skbs in clear text Todd Freed
2026-11-11  0:31 ` [PATCH nf-next v1 1/2] netfilter: bridge: don't refragment decrypted skbs Todd Freed
2026-11-11  0:31 ` Todd Freed [this message]
2026-11-11  0:36 ` [PATCH nf-next 0/2] netfilter: don't send decrypted skbs in clear text netdev-bot+sinfo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261111003111.3826942-3-toddfreed@meta.com \
    --to=toddfreed@meta.com \
    --cc=bjorn@kernel.org \
    --cc=borisp@nvidia.com \
    --cc=bridge@lists.linux.dev \
    --cc=coreteam@netfilter.org \
    --cc=daniel.zahka@gmail.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=fw@strlen.de \
    --cc=horms@kernel.org \
    --cc=idosch@nvidia.com \
    --cc=john.fastabend@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=netfilter-devel@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=pablo@netfilter.org \
    --cc=phil@nwl.cc \
    --cc=razor@blackwall.org \
    --cc=sd@queasysnail.net \
    --cc=willemdebruijn.kernel@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®