diff mbox series

[nf,3/4] netfilter: conntrack_ftp: prefer skb_linearize

Message ID 20220809131635.3376-4-fw@strlen.de
State Accepted
Delegated to: Pablo Neira
Headers show
Series netfilter: conntrack: remove 64kb max size assumptions | expand

Commit Message

Florian Westphal Aug. 9, 2022, 1:16 p.m. UTC
This uses a pseudo-linearization scheme with a 64k global buffer,
but BIG TCP arrival means IPv6 TCP stack can generate skbs
that exceed this size.

Use skb_linearize.  It should be possible to rewrite this to properly
deal with segmented skbs (i.e., only do small chunk-wise accesses),
but this is going to be a lot more intrusive than this because every
helper function needs to get the sk_buff instead of a pointer to a raw
data buffer.

In practice, provided we're really looking at FTP control channel packets,
there should never be a case where we deal with huge packets.

Fixes: 7c4e983c4f3c ("net: allow gso_max_size to exceed 65536")
Fixes: 0fe79f28bfaf ("net: allow gro_max_size to exceed 65536")
Signed-off-by: Florian Westphal <fw@strlen.de>
---
 net/netfilter/nf_conntrack_ftp.c | 24 ++++++------------------
 1 file changed, 6 insertions(+), 18 deletions(-)

Comments

Alexander Duyck Aug. 9, 2022, 3:36 p.m. UTC | #1
> -----Original Message-----
> From: Florian Westphal <fw@strlen.de>
> Sent: Tuesday, August 9, 2022 6:17 AM
> To: netfilter-devel@vger.kernel.org
> Cc: Alexander Duyck <alexanderduyck@fb.com>; edumazet@google.com;
> Florian Westphal <fw@strlen.de>
> Subject: [PATCH nf 3/4] netfilter: conntrack_ftp: prefer skb_linearize
> 
> > 
> This uses a pseudo-linearization scheme with a 64k global buffer, but BIG TCP
> arrival means IPv6 TCP stack can generate skbs that exceed this size.
> 
> Use skb_linearize.  It should be possible to rewrite this to properly deal with
> segmented skbs (i.e., only do small chunk-wise accesses), but this is going to
> be a lot more intrusive than this because every helper function needs to get
> the sk_buff instead of a pointer to a raw data buffer.
> 
> In practice, provided we're really looking at FTP control channel packets,
> there should never be a case where we deal with huge packets.
> 
> Fixes: 7c4e983c4f3c ("net: allow gso_max_size to exceed 65536")
> Fixes: 0fe79f28bfaf ("net: allow gro_max_size to exceed 65536")
> Signed-off-by: Florian Westphal <fw@strlen.de>
> ---
>  net/netfilter/nf_conntrack_ftp.c | 24 ++++++------------------
>  1 file changed, 6 insertions(+), 18 deletions(-)
> 
> diff --git a/net/netfilter/nf_conntrack_ftp.c
> b/net/netfilter/nf_conntrack_ftp.c
> index a414274338cf..0d9332e9cf71 100644
> --- a/net/netfilter/nf_conntrack_ftp.c
> +++ b/net/netfilter/nf_conntrack_ftp.c
> @@ -34,11 +34,6 @@ MODULE_DESCRIPTION("ftp connection tracking
> helper");  MODULE_ALIAS("ip_conntrack_ftp");
> MODULE_ALIAS_NFCT_HELPER(HELPER_NAME);
> 
> -/* This is slow, but it's simple. --RR */ -static char *ftp_buffer;
> -
> -static DEFINE_SPINLOCK(nf_ftp_lock);
> -
>  #define MAX_PORTS 8
>  static u_int16_t ports[MAX_PORTS];
>  static unsigned int ports_c;
> @@ -398,6 +393,9 @@ static int help(struct sk_buff *skb,
>  		return NF_ACCEPT;
>  	}
> 
> +	if (unlikely(skb_linearize(skb)))
> +		return NF_DROP;
> +
>  	th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
>  	if (th == NULL)
>  		return NF_ACCEPT;

Doing a full linearize seems like it would be much more expensive than it needs to be for a full frame.

> @@ -411,12 +409,8 @@ static int help(struct sk_buff *skb,
>  	}
>  	datalen = skb->len - dataoff;
> 
> -	spin_lock_bh(&nf_ftp_lock);
> -	fb_ptr = skb_header_pointer(skb, dataoff, datalen, ftp_buffer);
> -	if (!fb_ptr) {
> -		spin_unlock_bh(&nf_ftp_lock);
> -		return NF_ACCEPT;
> -	}
> +	spin_lock_bh(&ct->lock);
> +	fb_ptr = skb->data + dataoff;
> 
>  	ends_in_nl = (fb_ptr[datalen - 1] == '\n');
>  	seq = ntohl(th->seq) + datalen;

Rather than using skb_header_pointer/skb_linearize is there any reason why you couldn't use pskb_may_pull? It seems like that would be much closer to what you are looking for here rather than linearizing the entire buffer. With that you would have access to all the same headers you did with the skb_header_pointer approach and in most cases the copy should just be skipped since the headlen is already in the skb->data buffer.
Florian Westphal Aug. 9, 2022, 4:07 p.m. UTC | #2
Alexander Duyck <alexanderduyck@fb.com> wrote:
> > -	spin_lock_bh(&nf_ftp_lock);
> > -	fb_ptr = skb_header_pointer(skb, dataoff, datalen, ftp_buffer);
> > -	if (!fb_ptr) {
> > -		spin_unlock_bh(&nf_ftp_lock);
> > -		return NF_ACCEPT;
> > -	}
> > +	spin_lock_bh(&ct->lock);
> > +	fb_ptr = skb->data + dataoff;
> > 
> >  	ends_in_nl = (fb_ptr[datalen - 1] == '\n');
> >  	seq = ntohl(th->seq) + datalen;
> 
> Rather than using skb_header_pointer/skb_linearize is there any reason why you couldn't use pskb_may_pull? It seems like that would be much closer to what you are looking for here rather than linearizing the entire buffer. With that you would have access to all the same headers you did with the skb_header_pointer approach and in most cases the copy should just be skipped since the headlen is already in the skb->data buffer.

This helper is written with the assumption that everything is searchable
via 'const char *'.

I'm not going to submit a patch to -net that rewrites this,
and I'm not sure its worth it to spend time on it for -next either.
Alexander Duyck Aug. 9, 2022, 4:54 p.m. UTC | #3
> -----Original Message-----
> From: Florian Westphal <fw@strlen.de>
> Sent: Tuesday, August 9, 2022 9:07 AM
> To: Alexander Duyck <alexanderduyck@fb.com>
> Cc: Florian Westphal <fw@strlen.de>; netfilter-devel@vger.kernel.org;
> edumazet@google.com
> Subject: Re: [PATCH nf 3/4] netfilter: conntrack_ftp: prefer skb_linearize
> Alexander Duyck <alexanderduyck@fb.com> wrote:
> > > -	spin_lock_bh(&nf_ftp_lock);
> > > -	fb_ptr = skb_header_pointer(skb, dataoff, datalen, ftp_buffer);
> > > -	if (!fb_ptr) {
> > > -		spin_unlock_bh(&nf_ftp_lock);
> > > -		return NF_ACCEPT;
> > > -	}
> > > +	spin_lock_bh(&ct->lock);
> > > +	fb_ptr = skb->data + dataoff;
> > >
> > >  	ends_in_nl = (fb_ptr[datalen - 1] == '\n');
> > >  	seq = ntohl(th->seq) + datalen;
> >
> > Rather than using skb_header_pointer/skb_linearize is there any reason
> why you couldn't use pskb_may_pull? It seems like that would be much
> closer to what you are looking for here rather than linearizing the entire
> buffer. With that you would have access to all the same headers you did with
> the skb_header_pointer approach and in most cases the copy should just be
> skipped since the headlen is already in the skb->data buffer.
> 
> This helper is written with the assumption that everything is searchable via
> 'const char *'.
> 
> I'm not going to submit a patch to -net that rewrites this, and I'm not sure its
> worth it to spend time on it for -next either.

My bad, I misread it. I thought it was looking at the headers, instead this is looking at everything after the headers. I am honestly surprised it is using this approach since copying the entire buffer over to a linear buffer would be really expensive. I am assuming this code isn't exercised often? If so I guess skb_linearize works here, but it will be much more prone to failure for larger buffers as the higher order memory allocations will be likely to fail as memory gets more fragmented over time.
Florian Westphal Aug. 9, 2022, 10:38 p.m. UTC | #4
Alexander Duyck <alexanderduyck@fb.com> wrote:
> > why you couldn't use pskb_may_pull? It seems like that would be much
> > closer to what you are looking for here rather than linearizing the entire
> > buffer. With that you would have access to all the same headers you did with
> > the skb_header_pointer approach and in most cases the copy should just be
> > skipped since the headlen is already in the skb->data buffer.
> > 
> > This helper is written with the assumption that everything is searchable via
> > 'const char *'.
> > 
> > I'm not going to submit a patch to -net that rewrites this, and I'm not sure its
> > worth it to spend time on it for -next either.
> 
> My bad, I misread it. I thought it was looking at the headers, instead this is looking at everything after the headers. I am honestly surprised it is using this approach since copying the entire buffer over to a linear buffer would be really expensive. I am assuming this code isn't exercised often? If so I guess skb_linearize works here, but it will be much more prone to failure for larger buffers as the higher order memory allocations will be likely to fail as memory gets more fragmented over time.

It snoops the ftp control channel, so yes, this should be low overhead.

This code is ~20 years old, the original implementation is from a time when skbs were always linear.
diff mbox series

Patch

diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c
index a414274338cf..0d9332e9cf71 100644
--- a/net/netfilter/nf_conntrack_ftp.c
+++ b/net/netfilter/nf_conntrack_ftp.c
@@ -34,11 +34,6 @@  MODULE_DESCRIPTION("ftp connection tracking helper");
 MODULE_ALIAS("ip_conntrack_ftp");
 MODULE_ALIAS_NFCT_HELPER(HELPER_NAME);
 
-/* This is slow, but it's simple. --RR */
-static char *ftp_buffer;
-
-static DEFINE_SPINLOCK(nf_ftp_lock);
-
 #define MAX_PORTS 8
 static u_int16_t ports[MAX_PORTS];
 static unsigned int ports_c;
@@ -398,6 +393,9 @@  static int help(struct sk_buff *skb,
 		return NF_ACCEPT;
 	}
 
+	if (unlikely(skb_linearize(skb)))
+		return NF_DROP;
+
 	th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph);
 	if (th == NULL)
 		return NF_ACCEPT;
@@ -411,12 +409,8 @@  static int help(struct sk_buff *skb,
 	}
 	datalen = skb->len - dataoff;
 
-	spin_lock_bh(&nf_ftp_lock);
-	fb_ptr = skb_header_pointer(skb, dataoff, datalen, ftp_buffer);
-	if (!fb_ptr) {
-		spin_unlock_bh(&nf_ftp_lock);
-		return NF_ACCEPT;
-	}
+	spin_lock_bh(&ct->lock);
+	fb_ptr = skb->data + dataoff;
 
 	ends_in_nl = (fb_ptr[datalen - 1] == '\n');
 	seq = ntohl(th->seq) + datalen;
@@ -544,7 +538,7 @@  static int help(struct sk_buff *skb,
 	if (ends_in_nl)
 		update_nl_seq(ct, seq, ct_ftp_info, dir, skb);
  out:
-	spin_unlock_bh(&nf_ftp_lock);
+	spin_unlock_bh(&ct->lock);
 	return ret;
 }
 
@@ -571,7 +565,6 @@  static const struct nf_conntrack_expect_policy ftp_exp_policy = {
 static void __exit nf_conntrack_ftp_fini(void)
 {
 	nf_conntrack_helpers_unregister(ftp, ports_c * 2);
-	kfree(ftp_buffer);
 }
 
 static int __init nf_conntrack_ftp_init(void)
@@ -580,10 +573,6 @@  static int __init nf_conntrack_ftp_init(void)
 
 	NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_ftp_master));
 
-	ftp_buffer = kmalloc(65536, GFP_KERNEL);
-	if (!ftp_buffer)
-		return -ENOMEM;
-
 	if (ports_c == 0)
 		ports[ports_c++] = FTP_PORT;
 
@@ -603,7 +592,6 @@  static int __init nf_conntrack_ftp_init(void)
 	ret = nf_conntrack_helpers_register(ftp, ports_c * 2);
 	if (ret < 0) {
 		pr_err("failed to register helpers\n");
-		kfree(ftp_buffer);
 		return ret;
 	}