Netdev List
 help / color / mirror / Atom feed
From: Rongguang Wei <clementwei90@163.com>
To: netdev-bot+sashiko@kernel.org
Cc: netdev@vger.kernel.org, willemdebruijn.kernel@gmail.com,
	jasowangio@gmail.com, andrew+netdev@lunn.ch, davem@davemloft.net,
	kuba@kernel.org, weirongguang@kylinos.cn
Subject: Re: [PATCH net v4 1/2] tun: keep a kernel copy of the socket filter program
Date: Thu, 8 Oct 2026 14:45:51 +0800	[thread overview]
Message-ID: <00212619-8b3b-48a6-afa5-e2ad70c91fa1@163.com> (raw)
In-Reply-To: <179109599504.434549.16794375515097963280@kernel.org>



on 2026/10/4 14:39, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 0 · Medium: 1 (3) · Low: 1
> 
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
> 
> - [Medium] TUNATTACHFILTER can store an invalid classic BPF program and
>   still return 0.
> - [Low] The new exported helper sk_attach_filter_kern()
>   (net/core/filter.c:1570-1590) attaches a program built by…
> 
> Pre-existing issues:
> - [Medium] TUNDETACHFILTER does not remove the filter from detached
>   (disabled) queues, so a re-enabled queue keeps filtering with an old…
> - [Medium] TUNATTACHFILTER and TUNGETFILTER do not work for 32-bit user
>   space on a 64-bit CONFIG_COMPAT kernel.
> - [Medium] Replacing an attached filter leaves the device in an
>   inconsistent state if attaching the new one fails partway.
> 
> --
> 
> tun: keep a kernel copy of the socket filter program
> 
> This patch makes tun keep the classic BPF filter for TUNATTACHFILTER in
> kernel memory (tun->fprog_kern), so it no longer re-reads it from the user
> pointer every time a queue is attached. Each queue gets its own program
> from a new helper, sk_attach_filter_kern(). The patch also fixes the
> inverted error check in tun_attach().
> 
> Link: https://lore.kernel.org/netdev/179027275318.2160803.4185895144088175048@kernel.org/
> 
>> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
>> index 5a302709a68aa..b58ad67b77ad1 100644
>> --- a/drivers/net/tun.c
>> +++ b/drivers/net/tun.c
> 
> [ ... ]
> 
>> @@ -722,12 +723,47 @@ static void tun_force_wake_queue(struct tun_struct *tun,
>>  	spin_unlock_bh(&tfile->tx_ring.consumer_lock);
>>  }
>>  
>> +/* Copy the filter that @argp points at into the kernel, so that it can be
>> + * installed again later, independent of the ioctl caller's address space.
>> + * tun->fprog and tun->fprog_kern are updated only once the copy succeeded.
>> + */
>> +static int tun_copy_filter(struct tun_struct *tun, struct sock_fprog __user *argp)
>> +{
>> +	struct sock_fprog fprog;
>> +	struct sock_filter *insns;
>> +
>> +	if (copy_from_user(&fprog, argp, sizeof(fprog)))
>> +		return -EFAULT;
> 
> [Severity: Medium]
> This isn't a bug introduced by this patch, but does TUNATTACHFILTER work
> for 32-bit user space on a 64-bit CONFIG_COMPAT kernel?
> 
> TUNATTACHFILTER and TUNGETFILTER are defined with the size of struct
> sock_fprog in include/uapi/linux/if_tun.h:
> 
> #define TUNATTACHFILTER _IOW('T', 213, struct sock_fprog)
> 
> sock_fprog contains a pointer, so it is 8 bytes on 32-bit and 16 bytes on
> 64-bit, and the encoded command numbers differ. tun_chr_compat_ioctl()
> passes cmd through unchanged:
> 
> 	return __tun_chr_ioctl(file, cmd, arg, sizeof(struct compat_ifreq));
> 
> So the 32-bit numbers match no case in __tun_chr_ioctl() and fall through
> to the default tun_vnet_ioctl() branch.
> 
> Even if the numbers were normalised, this copy_from_user() reads the
> native sock_fprog layout, and TUNGETFILTER writes the native layout back.
> Nothing on this path converts to or from compat_sock_fprog.
> 
>> +
>> +	if (!fprog.len || fprog.len > BPF_MAXINSNS)
>> +		return -EINVAL;
> 
> [Severity: Medium]
> Can TUNATTACHFILTER now store an invalid classic BPF program and still
> return 0?
> 
The issue itself is pre-existing.  Before this series tun_attach_filter()
walked the same numqueues and set filter_attached = true either way, so an
invalid program was stored without ever being checked. And fix it where the
program is stored, but bpf_check_classic() is static in net/core/filter.c, 
so it needs either a new exported wrapper or a throwaway bpf_prog_create().  
Either way it changes what TUNATTACHFILTER accepts, so I would rather send 
that as its own patch than fold it into a series that is otherwise about 
re-attaching.
> This check covers only the length. The instructions are checked only by
> bpf_check_classic(), via
> sk_attach_filter_kern()->bpf_prog_create()->bpf_prepare_filter(). That
> runs once per attached queue in tun_attach_filter():
> 
> 	for (i = 0; i < tun->numqueues; i++) {
> 		...
> 	}
> 
> 	tun->filter_attached = true;
> 	return ret;
> 
> If tun->numqueues is 0, the loop never runs and the program is never
> checked. filter_attached is still set to true and the ioctl returns
> success.
> 
> This state can be reached on a multiqueue TAP. TUNSETQUEUE with
> IFF_DETACH_QUEUE goes through __tun_detach(tfile, false), which leaves
> tfile->tun set. The same fd can then issue TUNATTACHFILTER while no
> queues are attached.
> 
> With the corrected check in tun_attach():
> 
> 		err = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk);
> 		release_sock(tfile->socket.sk);
> 		if (err)
> 			goto out;
> 
> every later TUNSETQUEUE(IFF_ATTACH_QUEUE) through tun_set_queue(), and
> every TUNSETIFF without IFF_NOFILTER through tun_set_iff(), would then
> fail with -EINVAL from bpf_prepare_filter(). This lasts until someone
> issues TUNDETACHFILTER.
> 
> Could the instructions be checked once in the TUNATTACHFILTER path,
> whatever numqueues is? One option is to build and destroy a throwaway
> prog with bpf_prog_create(). Another is to run bpf_check_classic() on
> the copy.
> 
> There is also a smaller behaviour change. A bad header now makes the
> ioctl fail early and leaves the previous filter in place. Before, it
> failed on queue 0 and cleared filter_attached. Should the commit message
> mention this?
> 
I will add in the commit message: the length check in tun_copy_filter()
runs before anything is replaced, so a bad header now leaves the previously
attached filter in place, while before the failed attach on queue 0 cleared
filter_attached.
>> +
>> +	insns = kmalloc_array(fprog.len, sizeof(struct sock_filter),
>> +			      GFP_KERNEL_ACCOUNT);
> 
> [ ... ]
> 
>> @@ -3059,6 +3102,9 @@ static void tun_detach_filter(struct tun_struct *tun, int n)
>>  		release_sock(tfile->socket.sk);
>>  	}
>>  
>> +	kfree(tun->fprog_kern.filter);
>> +	tun->fprog_kern.filter = NULL;
>> +	tun->fprog_kern.len = 0;
>>  	tun->filter_attached = false;
>>  }
> 
> [Severity: Medium]
> This is a pre-existing issue, but does TUNDETACHFILTER leave the old
> filter on queues that are currently detached?
> 
> tun_detach_filter(tun, tun->numqueues) walks only tun->tfiles[0..n). A
> queue detached with IFF_DETACH_QUEUE sits on tun->disabled instead, and
> __tun_detach(tfile, false) does not touch its sk->sk_filter.
> 
> When that queue is attached again while filter_attached is false,
> tun_attach() skips the filter block:
> 
> 	if (!skip_filter && (tun->filter_attached == true)) {
> 
> Take this sequence: attach a filter, IFF_DETACH_QUEUE on queue Q,
> TUNDETACHFILTER, IFF_ATTACH_QUEUE on Q. Q keeps filtering packets with
> the removed program, while the device reports no filter.
> 
>>  
>> @@ -3070,7 +3116,7 @@ static int tun_attach_filter(struct tun_struct *tun)
>>  	for (i = 0; i < tun->numqueues; i++) {
>>  		tfile = rtnl_dereference(tun->tfiles[i]);
>>  		lock_sock(tfile->socket.sk);
>> -		ret = sk_attach_filter(&tun->fprog, tfile->socket.sk);
>> +		ret = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk);
>>  		release_sock(tfile->socket.sk);
>>  		if (ret) {
>>  			tun_detach_filter(tun, i);
> 
> [Severity: Medium]
> This isn't a bug introduced by this patch, but what happens to queues
> i..numqueues-1 when replacing an attached filter fails on queue i?
> 
> tun_detach_filter(tun, i) detaches only queues 0..i-1. It then frees
> fprog_kern and clears filter_attached (see tun_detach_filter() above).
> Queues i..numqueues-1 keep the old sk_filter.
> 
> Failure at i > 0 can happen through -ENOMEM from __sk_filter_charge() or
> the allocations. Failure at i == 0 can happen with an invalid program.
> 
> By this point tun_copy_filter() has already freed the old fprog_kern and
> set tun->fprog to the new program. After the failure:
> 
> - the device reports no filter
> - new queues attach without a filter
> - some existing queues still run the old program
> - TUNGETFILTER returns a program that is not attached anywhere
> 
> Memory safety is not affected, because bpf_prog_create() copies the
> instructions. The unwind logic is the same as before the patch.
> 
> [ ... ]
> 
>> diff --git a/net/core/filter.c b/net/core/filter.c
>> index 70dc621672f2e..64d6505a4ef23 100644
>> --- a/net/core/filter.c
>> +++ b/net/core/filter.c
>> @@ -1567,6 +1567,28 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
>>  }
>>  EXPORT_SYMBOL_GPL(sk_attach_filter);
>>  
>> +int sk_attach_filter_kern(struct sock_fprog_kern *fprog, struct sock *sk)
>> +{
>> +	struct bpf_prog *prog;
>> +	int err;
>> +
>> +	if (sock_flag(sk, SOCK_FILTER_LOCKED))
>> +		return -EPERM;
>> +
>> +	err = bpf_prog_create(&prog, fprog);
>> +	if (err)
>> +		return err;
> 
> [Severity: Low]
> This isn't a bug, but bpf_prog_create() assumes its result is never
> attached to a socket:
> 
> 	/* Since unattached filters are not copied back to user
> 	 * space through sk_get_filter(), we do not need to hold
> 	 * a copy here, and can spare us the work.
> 	 */
> 	fp->orig_prog = NULL;
> 
> sk_attach_filter_kern() now attaches exactly such a program to
> sk->sk_filter. When orig_prog is NULL, sk_get_filter() returns -EACCES
> and sock_diag_put_filterinfo() silently leaves the filter out.
> 
> The commit message notes that tun sockets do not expose this. Even so,
> the comment in bpf_prog_create() is now inaccurate. Unlike
> sk_attach_filter(), the new exported helper also has no kernel-doc to
> warn future callers.
> 
> Could sk_attach_filter_kern() save the original program, the way
> bpf_prog_create_from_user() does with save_orig set? If not, could it
> get kernel-doc that describes the limitation, along with an updated
> comment in bpf_prog_create()?
> 
No tun socket is ever exposed as a file descriptor, so SO_GET_FILTER and
sock_diag cannot observe the difference, while it would add an allocation
and a copy to every queue attach.
I will add kernel-doc for it in the respin.
> [ ... ]
> 
pw-bot: cr


  reply	other threads:[~2026-10-08  6:46 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-03  6:38 [PATCH net v4 0/2] tun: fix re-attaching the socket filter Rongguang Wei
2026-10-03  6:38 ` [PATCH net v4 1/2] tun: keep a kernel copy of the socket filter program Rongguang Wei
2026-10-03 18:46   ` Willem de Bruijn
2026-10-04  6:39   ` netdev-bot+sashiko
2026-10-08  6:45     ` Rongguang Wei [this message]
2026-10-03  6:38 ` [PATCH net v4 2/2] selftests: net: add TAP socket filter attach tests Rongguang Wei
2026-10-03 18:46   ` Willem de Bruijn
2026-10-04  6:39   ` netdev-bot+sashiko

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=00212619-8b3b-48a6-afa5-e2ad70c91fa1@163.com \
    --to=clementwei90@163.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=jasowangio@gmail.com \
    --cc=kuba@kernel.org \
    --cc=netdev-bot+sashiko@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=weirongguang@kylinos.cn \
    --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