* [PATCH net v2 0/4] tun: fix re-attaching the socket filter
@ 2026-09-29 9:37 Rongguang Wei
2026-09-29 9:37 ` [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter Rongguang Wei
` (3 more replies)
0 siblings, 4 replies; 17+ messages in thread
From: Rongguang Wei @ 2026-09-29 9:37 UTC (permalink / raw)
To: netdev
Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba,
Rongguang Wei
From: Rongguang Wei <weirongguang@kylinos.cn>
This is v2 of the series that fixes attaching a queue to a TAP device which
re-installs the socket filter of a persistent device.
Patch 1 fixes an inverted error check in tun_attach(): a successful
re-attach returned early, so the queue was never published in
tun->tfiles[] while TUNSETIFF still reported success. A failed re-attach
now aborts the attach, and the filter is detached again if a later step of
the attach fails.
Patch 2 adds sk_attach_filter_kern(), the kernel memory counterpart of
sk_attach_filter().
Patch 3 uses it to install a copy of the program that the kernel holds,
instead of reading tun->fprog from user space again on every later attach.
That is the second issue reported in the review of v1, fixed here together
with patch 1 as the review asked; IFF_NOFILTER keeps working and is no
longer needed as a workaround.
Patch 4 adds the selftest that a queue re-attached after the buffer the
program was copied from was made unreadable, a rejected TUNATTACHFILTER
that has to keep the saved descriptor, and attaching a queue without a
filter.
Rongguang Wei (4):
tun: fix inverted error check when re-attaching the filter
net: filter: add sk_attach_filter_kern() function
tun: keep a kernel copy of the socket filter program
selftests: net: add TAP socket filter attach tests
---
v2:
- roll back the filter attach when a later step of tun_attach() fails
- keep a copy of the program in the kernel, so that a later attach does
not depend on the address space of the process that set the filter
- add selftests for the re-attach and for a rejected TUNATTACHFILTER,
without patch 3, two of them fail.
v1:
- https://lore.kernel.org/netdev/20260923025653.59348-1-clementwei90@163.com/
---
drivers/net/tun.c | 50 +++++++++-
include/linux/filter.h | 1 +
net/core/filter.c | 22 +++++
tools/testing/selftests/net/tun.c | 159 ++++++++++++++++++++++++++++++
4 files changed, 227 insertions(+), 5 deletions(-)
--
2.43.0
No virus found
Checked by Hillstone Network AntiVirus
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter 2026-09-29 9:37 [PATCH net v2 0/4] tun: fix re-attaching the socket filter Rongguang Wei @ 2026-09-29 9:37 ` Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci 2026-10-01 4:39 ` netdev-bot+sashiko 2026-09-29 9:37 ` [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function Rongguang Wei ` (2 subsequent siblings) 3 siblings, 2 replies; 17+ messages in thread From: Rongguang Wei @ 2026-09-29 9:37 UTC (permalink / raw) To: netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, Rongguang Wei From: Rongguang Wei <weirongguang@kylinos.cn> tun_attach() returns early when sk_attach_filter() succeeds instead of when it fails, so a queue attached to a persistent TAP is never published in tun->tfiles[] while TUNSETIFF still reports success. Fix the check, so that a failed re-attach aborts the attach, and detach the filter again if a later step of tun_attach() fails. Fixes: 54f968d6efdb ("tuntap: move socket to tun_file") Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> --- v2: roll back the filter attach when a later step of tun_attach() fails v1: https://lore.kernel.org/netdev/20260923025653.59348-1-clementwei90@163.com/ --- drivers/net/tun.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5a302709a68a..a2fffef3735f 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -728,6 +728,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, { struct tun_file *tfile = file->private_data; struct net_device *dev = tun->dev; + bool rollback_filter = false; int err; err = security_tun_dev_attach(tfile->socket.sk, tun->security); @@ -754,8 +755,9 @@ static int tun_attach(struct tun_struct *tun, struct file *file, lock_sock(tfile->socket.sk); err = sk_attach_filter(&tun->fprog, tfile->socket.sk); release_sock(tfile->socket.sk); - if (!err) + if (err) goto out; + rollback_filter = true; } if (!tfile->detached && @@ -817,6 +819,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file, WRITE_ONCE(tun->numqueues, tun->numqueues + 1); tun_set_real_num_queues(tun); out: + if (err && rollback_filter) { + lock_sock(tfile->socket.sk); + sk_detach_filter(tfile->socket.sk); + release_sock(tfile->socket.sk); + } return err; } -- 2.25.1 No virus found Checked by Hillstone Network AntiVirus ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter 2026-09-29 9:37 ` [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter Rongguang Wei @ 2026-09-29 10:34 ` bot+bpf-ci 2026-09-30 2:23 ` weirongguang 2026-10-01 4:39 ` netdev-bot+sashiko 1 sibling, 1 reply; 17+ messages in thread From: bot+bpf-ci @ 2026-09-29 10:34 UTC (permalink / raw) To: clementwei90, netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, weirongguang, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 3840 bytes --] > diff --git a/drivers/net/tun.c b/drivers/net/tun.c > index 5a302709a68a..a2fffef3735f 100644 > --- a/drivers/net/tun.c > +++ b/drivers/net/tun.c > @@ -728,6 +728,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, > { > struct tun_file *tfile = file->private_data; > struct net_device *dev = tun->dev; > + bool rollback_filter = false; > int err; > > err = security_tun_dev_attach(tfile->socket.sk, tun->security); > @@ -754,8 +755,9 @@ static int tun_attach(struct tun_struct *tun, struct file *file, > lock_sock(tfile->socket.sk); > err = sk_attach_filter(&tun->fprog, tfile->socket.sk); > release_sock(tfile->socket.sk); > - if (!err) > + if (err) > goto out; > + rollback_filter = true; > } Does this change the behavior for cases where tun->fprog.filter holds a __user pointer from a different process? When TUNATTACHFILTER runs, it copies only the sock_fprog header: case TUNATTACHFILTER: ... if (copy_from_user(&tun->fprog, argp, sizeof(tun->fprog))) This means tun->fprog.filter still points into the address space of the process that issued that ioctl. When sk_attach_filter() runs here, it calls __get_filter() which does: copy_from_user(prog->insns, fprog->filter, fsize) in the context of the current process. For a persistent TAP, if process A creates it and runs TUNATTACHFILTER with a filter array on its stack or heap, then closes the fd, and process B later opens /dev/net/tun and runs TUNSETIFF on the same name without IFF_NOFILTER, the fprog.filter pointer may not be valid in process B's address space. Before this patch, when sk_attach_filter() failed (which was actually the success case due to the inverted check), the err value was overwritten by later operations (xdp_rxq_info_reg() followed by 'err = 0'), so the queue was published and TUNSETIFF succeeded, just without a filter installed. After this patch, if the pointer is unmapped, __get_filter() returns -EFAULT and TUNSETIFF now fails. If it happens to be mapped but holds other data, bpf_prepare_filter() usually returns -EINVAL and TUNSETIFF fails. When the bytes happen to pass validation, they become the filter of a queue that actually gets published. This affects both the persistent TAP case described above and multi-queue setups where another queue is opened after the buffer holding the original filter has been freed or reused. The next patch in the series, 508f529200c5 ("tun: keep a kernel copy of the socket filter program"), addresses this by storing a kernel copy of the filter program. That commit's changelog describes exactly this behavior: "unmapped there, the attach fails with -EFAULT; mapped, whatever bytes it holds become the filter of the new queue". It relies on 8c6a2cf8bd0a ("net: filter: add sk_attach_filter_kern() function"). Until that commit is applied, this patch changes the re-attach behavior from succeeding (without filter) to failing (with -EFAULT or -EINVAL). Both commits carry the same Fixes: tag, so a stable backport or bisect that includes only this commit would encounter the problem. Would it make sense to reorder these commits, putting the kernel-copy change before the inverted-check fix, so every step of the series works? > @@ -817,6 +819,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file, > WRITE_ONCE(tun->numqueues, tun->numqueues + 1); > tun_set_real_num_queues(tun); > out: > + if (err && rollback_filter) { > + lock_sock(tfile->socket.sk); > + sk_detach_filter(tfile->socket.sk); > + release_sock(tfile->socket.sk); > + } > return err; > } --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36552651982 ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter 2026-09-29 10:34 ` bot+bpf-ci @ 2026-09-30 2:23 ` weirongguang 0 siblings, 0 replies; 17+ messages in thread From: weirongguang @ 2026-09-30 2:23 UTC (permalink / raw) To: bot+bpf-ci, clementwei90, netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai on 2026/9/29 18:34, bot+bpf-ci@kernel.org wrote: >> diff --git a/drivers/net/tun.c b/drivers/net/tun.c >> index 5a302709a68a..a2fffef3735f 100644 >> --- a/drivers/net/tun.c >> +++ b/drivers/net/tun.c >> @@ -728,6 +728,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, >> { >> struct tun_file *tfile = file->private_data; >> struct net_device *dev = tun->dev; >> + bool rollback_filter = false; >> int err; >> >> err = security_tun_dev_attach(tfile->socket.sk, tun->security); >> @@ -754,8 +755,9 @@ static int tun_attach(struct tun_struct *tun, struct file *file, >> lock_sock(tfile->socket.sk); >> err = sk_attach_filter(&tun->fprog, tfile->socket.sk); >> release_sock(tfile->socket.sk); >> - if (!err) >> + if (err) >> goto out; >> + rollback_filter = true; >> } > > Does this change the behavior for cases where tun->fprog.filter holds a > __user pointer from a different process? > > When TUNATTACHFILTER runs, it copies only the sock_fprog header: > > case TUNATTACHFILTER: > ... > if (copy_from_user(&tun->fprog, argp, sizeof(tun->fprog))) > > This means tun->fprog.filter still points into the address space of the > process that issued that ioctl. When sk_attach_filter() runs here, it > calls __get_filter() which does: > > copy_from_user(prog->insns, fprog->filter, fsize) > > in the context of the current process. > > For a persistent TAP, if process A creates it and runs TUNATTACHFILTER > with a filter array on its stack or heap, then closes the fd, and process > B later opens /dev/net/tun and runs TUNSETIFF on the same name without > IFF_NOFILTER, the fprog.filter pointer may not be valid in process B's > address space. > > Before this patch, when sk_attach_filter() failed (which was actually the > success case due to the inverted check), the err value was overwritten by > later operations (xdp_rxq_info_reg() followed by 'err = 0'), so the queue > was published and TUNSETIFF succeeded, just without a filter installed. > > After this patch, if the pointer is unmapped, __get_filter() returns > -EFAULT and TUNSETIFF now fails. If it happens to be mapped but holds > other data, bpf_prepare_filter() usually returns -EINVAL and TUNSETIFF > fails. When the bytes happen to pass validation, they become the filter of > a queue that actually gets published. > > This affects both the persistent TAP case described above and multi-queue > setups where another queue is opened after the buffer holding the original > filter has been freed or reused. > > The next patch in the series, 508f529200c5 ("tun: keep a kernel copy of > the socket filter program"), addresses this by storing a kernel copy of > the filter program. That commit's changelog describes exactly this > behavior: "unmapped there, the attach fails with -EFAULT; mapped, > whatever bytes it holds become the filter of the new queue". It relies on > 8c6a2cf8bd0a ("net: filter: add sk_attach_filter_kern() function"). > > Until that commit is applied, this patch changes the re-attach behavior > from succeeding (without filter) to failing (with -EFAULT or -EINVAL). > Both commits carry the same Fixes: tag, so a stable backport or bisect > that includes only this commit would encounter the problem. > > Would it make sense to reorder these commits, putting the kernel-copy > change before the inverted-check fix, so every step of the series works? > Yes, thank you. I have reordered the series as you suggest, it now is: 1/4 net: filter: add sk_attach_filter_kern() function 2/4 tun: keep a kernel copy of the socket filter program 3/4 tun: fix inverted error check when re-attaching the filter 4/4 selftests: net: add TAP socket filter attach tests I will send it as v3. >> @@ -817,6 +819,11 @@ static int tun_attach(struct tun_struct *tun, struct file *file, >> WRITE_ONCE(tun->numqueues, tun->numqueues + 1); >> tun_set_real_num_queues(tun); >> out: >> + if (err && rollback_filter) { >> + lock_sock(tfile->socket.sk); >> + sk_detach_filter(tfile->socket.sk); >> + release_sock(tfile->socket.sk); >> + } >> return err; >> } > > > --- > AI reviewed your patch. Please fix the bug or email reply why it's not a bug. > See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md > > CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36552651982 ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter 2026-09-29 9:37 ` [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci @ 2026-10-01 4:39 ` netdev-bot+sashiko 1 sibling, 0 replies; 17+ messages in thread From: netdev-bot+sashiko @ 2026-10-01 4:39 UTC (permalink / raw) To: clementwei90 Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, weirongguang Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 · Low: 0 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] This patch un-inverts the check in tun_attach() (drivers/net/tun.c), so a failed filter re-attach now aborts TUNSETIFF… -- tun: fix inverted error check when re-attaching the filter This patch fixes the inverted sk_attach_filter() error check in tun_attach(). Before, a successful filter re-attach returned early and the queue was never published in tun->tfiles[]. Now a failed re-attach aborts the attach, and the filter is detached again if a later step of tun_attach() fails. > diff --git a/drivers/net/tun.c b/drivers/net/tun.c > index 5a302709a68aa..a2fffef3735fe 100644 > --- a/drivers/net/tun.c > +++ b/drivers/net/tun.c [ ... ] > @@ -754,8 +755,9 @@ static int tun_attach(struct tun_struct *tun, struct file *file, > lock_sock(tfile->socket.sk); > err = sk_attach_filter(&tun->fprog, tfile->socket.sk); > release_sock(tfile->socket.sk); > - if (!err) > + if (err) > goto out; > + rollback_filter = true; > } [Severity: Medium] A failed re-attach is now fatal. Can that make TUNSETIFF, or TUNSETQUEUE with IFF_ATTACH_QUEUE, fail on a persistent TAP that has a filter attached? tun->fprog only holds the {len, filter} header saved at TUNATTACHFILTER time, and filter is still a __user pointer: drivers/net/tun.c:__tun_chr_ioctl() { ... ret = -EFAULT; if (copy_from_user(&tun->fprog, argp, sizeof(tun->fprog))) break; ... } sk_attach_filter() then reads the instructions again, from the address space of whichever task is doing the attach: net/core/filter.c:__get_filter() { ... if (copy_from_user(prog->insns, fprog->filter, fsize)) ... } Consider this sequence: 1. Process A issues TUNATTACHFILTER. 2. A exits or frees the buffer. 3. Process B issues TUNSETIFF (without IFF_NOFILTER) or IFF_ATTACH_QUEUE. The copy in step 3 fails with -EFAULT, or the program fails validation with -EINVAL, and the attach is aborted. Before this patch the attach went ahead without a filter. If the stale address happens to be mapped in B, the filter is built from whatever bytes are at that address. tun_set_queue() always passes skip_filter=false, so IFF_ATTACH_QUEUE has no way to opt out: ret = tun_attach(tun, file, false, tun->flags & IFF_NAPI, tun->flags & IFF_NAPI_FRAGS, true); A later patch in this series, "tun: keep a kernel copy of the socket filter program", seems to fix this: - At TUNATTACHFILTER time, tun_copy_filter() copies the instructions into tun->fprog_kern. - tun_attach() and tun_attach_filter() switch to sk_attach_filter_kern(&tun->fprog_kern, ...). - The selftest reattach_filter_without_user_buffer() covers this case. Would it make sense to put that patch before this one? That way the stale user pointer read is never fatal at any point in the series. A smaller gap is still there at the end of the series. It predates this patch. If TUNATTACHFILTER is issued from a detached queue fd while numqueues is 0, tun_attach_filter() loops zero times. It then sets filter_attached = true without running bpf_check_classic(). Later attaches fail with -EINVAL until TUNDETACHFILTER is issued. [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929093712.131096-1-clementwei90%40163.com ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function 2026-09-29 9:37 [PATCH net v2 0/4] tun: fix re-attaching the socket filter Rongguang Wei 2026-09-29 9:37 ` [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter Rongguang Wei @ 2026-09-29 9:37 ` Rongguang Wei 2026-09-30 2:43 ` Willem de Bruijn 2026-10-01 4:39 ` netdev-bot+sashiko 2026-09-29 9:37 ` [PATCH v2 3/4] tun: keep a kernel copy of the socket filter program Rongguang Wei 2026-09-29 9:37 ` [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests Rongguang Wei 3 siblings, 2 replies; 17+ messages in thread From: Rongguang Wei @ 2026-09-29 9:37 UTC (permalink / raw) To: netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, Rongguang Wei From: Rongguang Wei <weirongguang@kylinos.cn> sk_attach_filter() copies the program from user space and sk_attach_bpf() takes it from a user file descriptor, so a program that the kernel keeps in memory cannot be installed again later. sk_attach_filter_kern() builds the program from a sock_fprog_kern, so no user buffer is read, and attaches it like sk_attach_filter(). The caller must hold the socket lock. Failing the attach releases it; so does the socket when the filter is replaced, detached or the socket goes away. Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> --- include/linux/filter.h | 1 + net/core/filter.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/include/linux/filter.h b/include/linux/filter.h index 39decde7fc73..0de5a738fb26 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -1218,6 +1218,7 @@ int bpf_prog_create_from_user(struct bpf_prog **pfp, struct sock_fprog *fprog, void bpf_prog_destroy(struct bpf_prog *fp); int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk); +int sk_attach_filter_kern(struct sock_fprog_kern *fprog, struct sock *sk); int sk_attach_bpf(u32 ufd, struct sock *sk); int sk_reuseport_attach_filter(struct sock_fprog *fprog, struct sock *sk); int sk_reuseport_attach_bpf(u32 ufd, struct sock *sk); diff --git a/net/core/filter.c b/net/core/filter.c index 70dc621672f2..64d6505a4ef2 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; + + err = __sk_attach_prog(prog, sk); + if (err < 0) { + __bpf_prog_release(prog); + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(sk_attach_filter_kern); + int sk_reuseport_attach_filter(struct sock_fprog *fprog, struct sock *sk) { struct bpf_prog *prog = __get_filter(fprog, sk); -- 2.25.1 No virus found Checked by Hillstone Network AntiVirus ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function 2026-09-29 9:37 ` [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function Rongguang Wei @ 2026-09-30 2:43 ` Willem de Bruijn 2026-09-30 6:28 ` Rongguang Wei 2026-10-01 4:39 ` netdev-bot+sashiko 1 sibling, 1 reply; 17+ messages in thread From: Willem de Bruijn @ 2026-09-30 2:43 UTC (permalink / raw) To: Rongguang Wei, netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, Rongguang Wei Rongguang Wei wrote: > From: Rongguang Wei <weirongguang@kylinos.cn> > > sk_attach_filter() copies the program from user space and sk_attach_bpf() > takes it from a user file descriptor, so a program that the kernel keeps in > memory cannot be installed again later. > > sk_attach_filter_kern() builds the program from a sock_fprog_kern, so no > user buffer is read, and attaches it like sk_attach_filter(). The caller > must hold the socket lock. Failing the attach releases it; so does the > socket when the filter is replaced, detached or the socket goes away. > > Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> This should probably be squashed into the next commit, that first uses it. > --- > include/linux/filter.h | 1 + > net/core/filter.c | 22 ++++++++++++++++++++++ > 2 files changed, 23 insertions(+) > > diff --git a/include/linux/filter.h b/include/linux/filter.h > index 39decde7fc73..0de5a738fb26 100644 > --- a/include/linux/filter.h > +++ b/include/linux/filter.h > @@ -1218,6 +1218,7 @@ int bpf_prog_create_from_user(struct bpf_prog **pfp, struct sock_fprog *fprog, > void bpf_prog_destroy(struct bpf_prog *fp); > > int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk); > +int sk_attach_filter_kern(struct sock_fprog_kern *fprog, struct sock *sk); > int sk_attach_bpf(u32 ufd, struct sock *sk); > int sk_reuseport_attach_filter(struct sock_fprog *fprog, struct sock *sk); > int sk_reuseport_attach_bpf(u32 ufd, struct sock *sk); > diff --git a/net/core/filter.c b/net/core/filter.c > index 70dc621672f2..64d6505a4ef2 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; > + > + err = __sk_attach_prog(prog, sk); > + if (err < 0) { > + __bpf_prog_release(prog); > + return err; > + } > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(sk_attach_filter_kern); > + > int sk_reuseport_attach_filter(struct sock_fprog *fprog, struct sock *sk) > { > struct bpf_prog *prog = __get_filter(fprog, sk); > -- > 2.25.1 > > > No virus found > Checked by Hillstone Network AntiVirus > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function 2026-09-30 2:43 ` Willem de Bruijn @ 2026-09-30 6:28 ` Rongguang Wei 0 siblings, 0 replies; 17+ messages in thread From: Rongguang Wei @ 2026-09-30 6:28 UTC (permalink / raw) To: Willem de Bruijn, netdev Cc: jasowangio, andrew+netdev, davem, kuba, Rongguang Wei on 2026/9/30 10:43, Willem de Bruijn wrote: > Rongguang Wei wrote: >> From: Rongguang Wei <weirongguang@kylinos.cn> >> >> sk_attach_filter() copies the program from user space and sk_attach_bpf() >> takes it from a user file descriptor, so a program that the kernel keeps in >> memory cannot be installed again later. >> >> sk_attach_filter_kern() builds the program from a sock_fprog_kern, so no >> user buffer is read, and attaches it like sk_attach_filter(). The caller >> must hold the socket lock. Failing the attach releases it; so does the >> socket when the filter is replaced, detached or the socket goes away. >> >> Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> > > This should probably be squashed into the next commit, that first uses it. > Agreed, it is now part of that patch. I will send the series as v3. >> --- >> include/linux/filter.h | 1 + >> net/core/filter.c | 22 ++++++++++++++++++++++ >> 2 files changed, 23 insertions(+) >> >> diff --git a/include/linux/filter.h b/include/linux/filter.h >> index 39decde7fc73..0de5a738fb26 100644 >> --- a/include/linux/filter.h >> +++ b/include/linux/filter.h >> @@ -1218,6 +1218,7 @@ int bpf_prog_create_from_user(struct bpf_prog **pfp, struct sock_fprog *fprog, >> void bpf_prog_destroy(struct bpf_prog *fp); >> >> int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk); >> +int sk_attach_filter_kern(struct sock_fprog_kern *fprog, struct sock *sk); >> int sk_attach_bpf(u32 ufd, struct sock *sk); >> int sk_reuseport_attach_filter(struct sock_fprog *fprog, struct sock *sk); >> int sk_reuseport_attach_bpf(u32 ufd, struct sock *sk); >> diff --git a/net/core/filter.c b/net/core/filter.c >> index 70dc621672f2..64d6505a4ef2 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; >> + >> + err = __sk_attach_prog(prog, sk); >> + if (err < 0) { >> + __bpf_prog_release(prog); >> + return err; >> + } >> + >> + return 0; >> +} >> +EXPORT_SYMBOL_GPL(sk_attach_filter_kern); >> + >> int sk_reuseport_attach_filter(struct sock_fprog *fprog, struct sock *sk) >> { >> struct bpf_prog *prog = __get_filter(fprog, sk); >> -- >> 2.25.1 >> >> >> No virus found >> Checked by Hillstone Network AntiVirus >> > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function 2026-09-29 9:37 ` [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function Rongguang Wei 2026-09-30 2:43 ` Willem de Bruijn @ 2026-10-01 4:39 ` netdev-bot+sashiko 1 sibling, 0 replies; 17+ messages in thread From: netdev-bot+sashiko @ 2026-10-01 4:39 UTC (permalink / raw) To: clementwei90 Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, weirongguang Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 · 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] sk_attach_filter_kern() compiles stored classic BPF instructions in whatever task happens to call it. - [Low] sk_attach_filter_kern() (net/core/filter.c:1570-1590) builds its program with bpf_prog_create() and publishes it in sk->sk_filter… -- net: filter: add sk_attach_filter_kern() function This adds sk_attach_filter_kern(). It builds a classic BPF socket filter from a kernel-resident sock_fprog_kern using bpf_prog_create(), then attaches it with __sk_attach_prog(). The kernel can then reinstall a stored filter without reading a user buffer or file descriptor again. > 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); [Severity: Low] bpf_prog_create() is documented as creating an unattached filter. It drops the original program on purpose: net/core/filter.c:bpf_prog_create() { ... /* 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; ... } With this new export, that program ends up in sk->sk_filter. Can a filter attached this way still be read back? When orig_prog is NULL, sk_get_filter() returns -EACCES. Its comment says this only happens for eBPF programs: net/core/filter.c:sk_get_filter() { ... /* We're copying the filter that has been originally attached, * so no conversion/decode needed anymore. eBPF programs that * have no original program cannot be dumped through this. */ ret = -EACCES; fprog = filter->prog->orig_prog; if (!fprog) goto out; ... } In the same case, sock_diag_put_filterinfo() leaves out the filter attribute without any error. sk_attach_filter() goes through __get_filter()->bpf_prog_store_orig_filter() and keeps the original program. So for classic filters, the two exported attach APIs now behave differently for SO_GET_FILTER and sock_diag. The tun consumer later in this series does not seem to be affected in a way userspace can see. SO_GET_FILTER and sock_diag cannot reach its sockets, and TUNGETFILTER returns tun->fprog. Could this store orig_prog, the way bpf_prog_create_from_user() does with save_orig? If not, could the limitation be documented and the comments in bpf_prog_create() and sk_get_filter() updated? [Severity: Medium] Does this change whose credentials decide JIT hardening? bpf_prog_create() calls bpf_prog_alloc(), and bpf_prog_alloc_no_stats() sets: fp->blinding_requested = bpf_jit_blinding_enabled(fp); When bpf_jit_harden is 1, bpf_jit_blinding_enabled() decides based on current: if (bpf_jit_harden == 1 && bpf_token_capable(prog->aux->token, CAP_BPF)) return false; The instructions come from whoever stored them in the sock_fprog_kern. Blinding, however, depends on whichever task calls sk_attach_filter_kern() later. With the tun consumer later in this series, this sequence looks possible: 1. An unprivileged owner of a persistent multi-queue TAP passes tun_not_capable() and issues TUNATTACHFILTER. The instructions are stored in tun->fprog_kern. The programs built for the queues attached at that point are blinded. 2. Later, a process with CAP_BPF or CAP_SYS_ADMIN attaches another queue using TUNSETIFF or TUNSETQUEUE. 3. tun_attach() calls sk_attach_filter_kern(&tun->fprog_kern, ...) with the privileged credentials. The unprivileged user's filter goes through bpf_prepare_filter()->bpf_migrate_filter()-> bpf_prog_select_runtime() and is JIT-compiled without constant blinding. Before this change, sk_attach_filter(&tun->fprog, ...) read the instructions again from the attaching process's own address space. The unprivileged user did not control the bytes compiled in the privileged context. With bpf_jit_harden=1, does this let constants chosen by the user into JIT memory that is not blinded? Could the blinding decision be recorded when the instructions are supplied, or be tied to where the program came from rather than to current at attach time? [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929093712.131096-1-clementwei90%40163.com ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 3/4] tun: keep a kernel copy of the socket filter program 2026-09-29 9:37 [PATCH net v2 0/4] tun: fix re-attaching the socket filter Rongguang Wei 2026-09-29 9:37 ` [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter Rongguang Wei 2026-09-29 9:37 ` [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function Rongguang Wei @ 2026-09-29 9:37 ` Rongguang Wei 2026-10-01 4:39 ` netdev-bot+sashiko 2026-09-29 9:37 ` [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests Rongguang Wei 3 siblings, 1 reply; 17+ messages in thread From: Rongguang Wei @ 2026-09-29 9:37 UTC (permalink / raw) To: netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, Rongguang Wei From: Rongguang Wei <weirongguang@kylinos.cn> TUNATTACHFILTER copies only the sock_fprog header, so tun->fprog.filter stays a pointer into the address space of the process that issued the ioctl. tun_attach() reads it again whenever a queue is attached to the persistent device later on: unmapped there, the attach fails with -EFAULT; mapped, whatever bytes it holds become the filter of the new queue. Keep the program in the kernel instead. tun->fprog_kern holds the instructions and each queue gets its own program, built with sk_attach_filter_kern(). The copy is freed when the filter is detached or replaced and with the device, and tun->fprog is left untouched so TUNGETFILTER keeps its uapi behaviour. Fixes: 54f968d6efdb ("tuntap: move socket to tun_file") Link: https://lore.kernel.org/netdev/179027275318.2160803.4185895144088175048@kernel.org/ Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> --- drivers/net/tun.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/drivers/net/tun.c b/drivers/net/tun.c index a2fffef3735f..c796048742f9 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -197,6 +197,7 @@ struct tun_struct { int sndbuf; struct tap_filter txflt; struct sock_fprog fprog; + struct sock_fprog_kern fprog_kern; /* protected by rtnl lock */ bool filter_attached; u32 msg_enable; @@ -722,6 +723,34 @@ 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, from any context. 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; + + if (!fprog.len || fprog.len > BPF_MAXINSNS) + return -EINVAL; + + insns = memdup_array_user(fprog.filter, fprog.len, + sizeof(struct sock_filter)); + if (IS_ERR(insns)) + return PTR_ERR(insns); + + kfree(tun->fprog_kern.filter); + tun->fprog_kern.len = fprog.len; + tun->fprog_kern.filter = insns; + tun->fprog = fprog; + + return 0; +} + static int tun_attach(struct tun_struct *tun, struct file *file, bool skip_filter, bool napi, bool napi_frags, bool publish_tun) @@ -753,7 +782,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, /* Re-attach the filter to persist device */ if (!skip_filter && (tun->filter_attached == true)) { lock_sock(tfile->socket.sk); - err = sk_attach_filter(&tun->fprog, tfile->socket.sk); + err = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk); release_sock(tfile->socket.sk); if (err) goto out; @@ -2404,6 +2433,7 @@ static void tun_free_netdev(struct net_device *dev) security_tun_dev_free_security(tun->security); __tun_set_ebpf(tun, &tun->steering_prog, NULL); __tun_set_ebpf(tun, &tun->filter_prog, NULL); + kfree(tun->fprog_kern.filter); } static void tun_setup(struct net_device *dev) @@ -3066,6 +3096,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; } @@ -3077,7 +3110,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); @@ -3425,8 +3458,8 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, ret = -EINVAL; if ((tun->flags & TUN_TYPE_MASK) != IFF_TAP) break; - ret = -EFAULT; - if (copy_from_user(&tun->fprog, argp, sizeof(tun->fprog))) + ret = tun_copy_filter(tun, argp); + if (ret) break; ret = tun_attach_filter(tun); -- 2.25.1 No virus found Checked by Hillstone Network AntiVirus ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v2 3/4] tun: keep a kernel copy of the socket filter program 2026-09-29 9:37 ` [PATCH v2 3/4] tun: keep a kernel copy of the socket filter program Rongguang Wei @ 2026-10-01 4:39 ` netdev-bot+sashiko 0 siblings, 0 replies; 17+ messages in thread From: netdev-bot+sashiko @ 2026-10-01 4:39 UTC (permalink / raw) To: clementwei90 Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, weirongguang Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin. Low priority / nit picks for your consideration: - [Low] This commit carries `Fixes: 54f968d6efdb`, so it will be picked for stable. - [Low] For TUNATTACHFILTER, tun_copy_filter() replaces the core bpf_check_basics_ok() validation with its own checks, and the two… Pre-existing issues: - [Medium] This problem wasn't introduced by this patch, but a failed filter replacement leaves the queues in mixed states. - [Medium] This problem wasn't introduced by this patch, but TUNDETACHFILTER only detaches the filter from the tun->numqueues… - [Low] This problem wasn't introduced by this patch, but when every queue of a multiqueue TAP is disabled with IFF_DETACH_QUEUE,… -- tun: keep a kernel copy of the socket filter program With this commit, TUNATTACHFILTER copies the classic BPF instructions into tun->fprog_kern instead of keeping only the user pointer in tun->fprog. tun_attach() and tun_attach_filter() then build each queue's filter from that kernel copy through sk_attach_filter_kern(). The copy is freed on detach, when it is replaced, and in tun_free_netdev(). Link: https://lore.kernel.org/netdev/179027275318.2160803.4185895144088175048@kernel.org/ > diff --git a/drivers/net/tun.c b/drivers/net/tun.c > index a2fffef3735fe..c796048742f9b 100644 > --- a/drivers/net/tun.c > +++ b/drivers/net/tun.c [ ... ] > @@ -722,6 +723,34 @@ 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, from any context. 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; > + > + if (!fprog.len || fprog.len > BPF_MAXINSNS) > + return -EINVAL; > + > + insns = memdup_array_user(fprog.filter, fprog.len, > + sizeof(struct sock_filter)); [Severity: Low] Does this change the errno TUNATTACHFILTER returns when fprog.filter is NULL and fprog.len is nonzero? Before this patch, sk_attach_filter()->__get_filter()->bpf_check_basics_ok() rejected a NULL filter with -EINVAL, the same errno SO_ATTACH_FILTER returns. Here only the length is checked. memdup_array_user(NULL, ...) then fails in copy_from_user(), and the ioctl returns -EFAULT. The bpf_check_basics_ok() call inside bpf_prog_create() only sees the kernel copy, which is never NULL, so it can't catch this case. Would adding a !fprog.filter test to the length check keep the old -EINVAL? > + if (IS_ERR(insns)) > + return PTR_ERR(insns); > + > + kfree(tun->fprog_kern.filter); > + tun->fprog_kern.len = fprog.len; > + tun->fprog_kern.filter = insns; > + tun->fprog = fprog; > + > + return 0; > +} > + > static int tun_attach(struct tun_struct *tun, struct file *file, > bool skip_filter, bool napi, bool napi_frags, > bool publish_tun) > @@ -753,7 +782,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file, > /* Re-attach the filter to persist device */ > if (!skip_filter && (tun->filter_attached == true)) { > lock_sock(tfile->socket.sk); > - err = sk_attach_filter(&tun->fprog, tfile->socket.sk); > + err = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk); [Severity: Low] Should this commit record that it depends on the helper commit? It carries Fixes: 54f968d6efdb, so it will be picked for stable. sk_attach_filter_kern() is only added by the previous commit in the series, "net: filter: add sk_attach_filter_kern() function". That commit has no Fixes: tag, no Cc: stable and no stable dependency annotation. This commit's message doesn't say it depends on that commit, or on "tun: fix inverted error check when re-attaching the filter". If it is backported by itself, tun_attach() and tun_attach_filter() won't build. sk_attach_filter_kern is implicitly declared, and the symbol is undefined when tun is built as a module. Would a Cc: stable tag with a dependency note on the helper commit prevent that? > release_sock(tfile->socket.sk); > if (err) > goto out; [ ... ] > @@ -3066,6 +3096,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 isn't a bug introduced by this patch, but does TUNDETACHFILTER leave the filter on queues that are disabled at that point? tun_detach_filter() only walks the first n == tun->numqueues entries: for (i = 0; i < n; i++) { tfile = rtnl_dereference(tun->tfiles[i]); lock_sock(tfile->socket.sk); sk_detach_filter(tfile->socket.sk); ... A queue disabled through TUNSETQUEUE with IFF_DETACH_QUEUE (tun_set_queue()->__tun_detach(tfile, false)) keeps its sk_filter. When that queue is re-enabled with IFF_ATTACH_QUEUE, tun_attach() skips the filter block because filter_attached is false. It doesn't clear the leftover filter either. tun_net_xmit() checks tfile->socket.sk->sk_filter directly. So after TUNDETACHFILTER succeeds, the re-enabled queue still applies the old filter. > } > > @@ -3077,7 +3110,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 is a pre-existing issue, but can a failed filter replacement leave the queues in mixed states? Say filter A is attached and TUNATTACHFILTER is issued with program B. tun_copy_filter() frees A's kernel copy and stores B. tun_attach_filter() then fails on queue i. That happens if bpf_check_classic() rejects B (i == 0), or if bpf_prog_alloc() or __sk_filter_charge() fails at any i. tun_detach_filter(tun, i) detaches only queues [0, i). It also frees B's copy and sets filter_attached = false. Queues [i, numqueues) keep running filter A, but the device-level state says no filter is attached. Queues attached later through tun_attach() get no filter at all. The code behaved the same way before this patch. > @@ -3425,8 +3458,8 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd, > ret = -EINVAL; > if ((tun->flags & TUN_TYPE_MASK) != IFF_TAP) > break; > - ret = -EFAULT; > - if (copy_from_user(&tun->fprog, argp, sizeof(tun->fprog))) > + ret = tun_copy_filter(tun, argp); > + if (ret) > break; > > ret = tun_attach_filter(tun); [Severity: Low] This isn't a bug introduced by this patch, but what happens when every queue of a multiqueue TAP has been disabled with IFF_DETACH_QUEUE? tun->numqueues is 0 then, but tfile->tun is still set, so tun_get() still reaches TUNATTACHFILTER. tun_copy_filter() checks only the length and copies the instructions. The loop in tun_attach_filter() runs zero times, so filter_attached is set to true and 0 is returned. bpf_check_classic() never runs. A later IFF_ATTACH_QUEUE calls sk_attach_filter_kern() from tun_attach(). bpf_prepare_filter() then rejects the saved program with -EINVAL. The queue can't be re-enabled until the owner issues TUNDETACHFILTER or a valid TUNATTACHFILTER. This patch narrows the problem a little: before its length check, this path accepted even len == 0. -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929093712.131096-1-clementwei90%40163.com ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests 2026-09-29 9:37 [PATCH net v2 0/4] tun: fix re-attaching the socket filter Rongguang Wei ` (2 preceding siblings ...) 2026-09-29 9:37 ` [PATCH v2 3/4] tun: keep a kernel copy of the socket filter program Rongguang Wei @ 2026-09-29 9:37 ` Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci ` (2 more replies) 3 siblings, 3 replies; 17+ messages in thread From: Rongguang Wei @ 2026-09-29 9:37 UTC (permalink / raw) To: netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, Rongguang Wei From: Rongguang Wei <weirongguang@kylinos.cn> reattach_filter_without_user_buffer() attaches a filter, makes the buffer the program was copied from unreadable and then attaches the queue again, which used to fail with -EFAULT. attach_filter_bad_len_keeps_descriptor() checks that a rejected TUNATTACHFILTER leaves both the saved descriptor and the installed filter alone. attach_filter_nofilter_flag() and detach_filter_clears_reattach() cover the ways to attach a queue without a filter. Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> --- tools/testing/selftests/net/tun.c | 159 ++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index abe488bac50b..bf8d4d6f21f7 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -8,13 +8,19 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <linux/filter.h> #include <linux/if_tun.h> #include <sys/ioctl.h> +#include <sys/mman.h> #include <sys/socket.h> #include "kselftest_harness.h" #include "tuntap_helpers.h" +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + static const char param_dev_geneve_name[] = "geneve1"; static unsigned char param_hwaddr_outer_dst[] = { 0x00, 0xfe, 0x98, 0x14, 0x22, 0x42 }; @@ -542,6 +548,159 @@ TEST_F(tun, reattach_close_delete) EXPECT_EQ(tun_delete(self->ifname), 0); } +/* accept: return skb->len */ +static const struct sock_filter filter_accept[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_LEN, 0), + BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0), + BPF_STMT(BPF_RET | BPF_A, 0), +}; + +/* drop: return 0 */ +static const struct sock_filter filter_drop[] = { + BPF_STMT(BPF_RET | BPF_K, 0), +}; + +/* Put the instructions in an anonymous mapping, so that the test can make the + * address unreadable afterwards. + */ +static void *filter_alloc(const struct sock_filter *insns, unsigned int len) +{ + void *p; + + p = mmap(NULL, getpagesize(), PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) + return NULL; + + memcpy(p, insns, len * sizeof(*insns)); + + return p; +} + +static int filter_attach(int fd, const struct sock_filter *insns, unsigned int len) +{ + struct sock_fprog fp = { + .len = len, + .filter = (struct sock_filter *)insns, + }; + + return ioctl(fd, TUNATTACHFILTER, (void *)&fp); +} + +static int filter_get(int fd, struct sock_fprog *fp) +{ + return ioctl(fd, TUNGETFILTER, (void *)fp); +} + +static int tap_get_iff(int fd, short *flags) +{ + struct ifreq ifr = { 0 }; + + if (ioctl(fd, TUNGETIFF, (void *)&ifr) < 0) + return -1; + + *flags = ifr.ifr_flags; + + return 0; +} + +/* + * A queue can be attached long after the filter was configured, from a process + * that does not necessarily map the buffer the program was copied from, so + * the kernel has to keep its own copy of the program. Here the mapping is + * made unreadable before the queue is attached again: re-reading the user + * address used to fail with -EFAULT. + */ +TEST_F(tun, reattach_filter_without_user_buffer) +{ + struct sock_fprog gf = { 0 }; + short flags = 0; + void *prog; + int ret; + + prog = filter_alloc(filter_accept, ARRAY_SIZE(filter_accept)); + ASSERT_NE(prog, NULL); + ASSERT_EQ(filter_attach(self->fd, prog, ARRAY_SIZE(filter_accept)), 0); + + EXPECT_EQ(tun_detach(self->fd, self->ifname), 0); + + /* The process that called TUNATTACHFILTER no longer maps this memory */ + ASSERT_EQ(mprotect(prog, getpagesize(), PROT_NONE), 0); + + ret = tun_attach(self->fd, self->ifname); + EXPECT_EQ(ret, 0); + + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); + EXPECT_EQ(flags & IFF_NOFILTER, 0); + + EXPECT_EQ(filter_get(self->fd, &gf), 0); + EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept)); + + ASSERT_EQ(mprotect(prog, getpagesize(), PROT_READ | PROT_WRITE), 0); + ASSERT_EQ(munmap(prog, getpagesize()), 0); +} + +/* A new queue attached with IFF_NOFILTER must not get the filter that is + * configured on the device. + */ +TEST_F(tun, attach_filter_nofilter_flag) +{ + struct ifreq ifr = { 0 }; + short flags = 0; + int fd; + + ASSERT_EQ(filter_attach(self->fd, filter_drop, ARRAY_SIZE(filter_drop)), 0); + + fd = open("/dev/net/tun", O_RDWR); + ASSERT_GE(fd, 0); + + strcpy(ifr.ifr_name, self->ifname); + ifr.ifr_flags = IFF_TAP | IFF_MULTI_QUEUE | IFF_NOFILTER; + EXPECT_GE(ioctl(fd, TUNSETIFF, (void *)&ifr), 0); + + EXPECT_EQ(tap_get_iff(fd, &flags), 0); + EXPECT_NE(flags & IFF_NOFILTER, 0); + + close(fd); +} + +/* After TUNDETACHFILTER a later attach must not install the filter again */ +TEST_F(tun, detach_filter_clears_reattach) +{ + short flags = 0; + + ASSERT_EQ(filter_attach(self->fd, filter_drop, ARRAY_SIZE(filter_drop)), 0); + EXPECT_EQ(ioctl(self->fd, TUNDETACHFILTER, 0), 0); + + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); + EXPECT_NE(flags & IFF_NOFILTER, 0); + + EXPECT_EQ(tun_detach(self->fd, self->ifname), 0); + EXPECT_EQ(tun_attach(self->fd, self->ifname), 0); + + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); + EXPECT_NE(flags & IFF_NOFILTER, 0); +} + +/* A TUNATTACHFILTER with a bad length must not clobber the saved descriptor */ +TEST_F(tun, attach_filter_bad_len_keeps_descriptor) +{ + struct sock_fprog gf = { 0 }; + short flags = 0; + + ASSERT_EQ(filter_attach(self->fd, filter_accept, ARRAY_SIZE(filter_accept)), 0); + + errno = 0; + EXPECT_EQ(filter_attach(self->fd, filter_accept, 0), -1); + EXPECT_EQ(errno, EINVAL); + + EXPECT_EQ(filter_get(self->fd, &gf), 0); + EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept)); + + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); + EXPECT_EQ(flags & IFF_NOFILTER, 0); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; -- 2.25.1 No virus found Checked by Hillstone Network AntiVirus ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests 2026-09-29 9:37 ` [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests Rongguang Wei @ 2026-09-29 10:34 ` bot+bpf-ci 2026-09-30 2:39 ` weirongguang 2026-09-30 2:48 ` Willem de Bruijn 2026-10-01 4:39 ` netdev-bot+sashiko 2 siblings, 1 reply; 17+ messages in thread From: bot+bpf-ci @ 2026-09-29 10:34 UTC (permalink / raw) To: clementwei90, netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, weirongguang, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 1289 bytes --] > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > index abe488bac50b..bf8d4d6f21f7 100644 > --- a/tools/testing/selftests/net/tun.c > +++ b/tools/testing/selftests/net/tun.c > @@ -8,13 +8,19 @@ > #include <stdlib.h> > #include <string.h> > #include <unistd.h> > +#include <linux/filter.h> > #include <linux/if_tun.h> > #include <sys/ioctl.h> > +#include <sys/mman.h> > #include <sys/socket.h> > > #include "kselftest_harness.h" > #include "tuntap_helpers.h" > > +#ifndef ARRAY_SIZE > +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) > +#endif This isn't a bug, but kselftest.h already provides ARRAY_SIZE through kselftest_harness.h. Is this #ifndef block needed, or can it be dropped? Looking at the include chain: kselftest_harness.h includes kselftest.h, and kselftest.h defines ARRAY_SIZE at line 63-65. kselftest_harness.h also defines it again at line 749. Other harness-based tests in the same directory (tls.c, ip_local_port_range.c) use ARRAY_SIZE without defining it themselves. [ ... ] --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36552651982 ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests 2026-09-29 10:34 ` bot+bpf-ci @ 2026-09-30 2:39 ` weirongguang 0 siblings, 0 replies; 17+ messages in thread From: weirongguang @ 2026-09-30 2:39 UTC (permalink / raw) To: bot+bpf-ci, clementwei90, netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai on 2026/9/29 18:34, bot+bpf-ci@kernel.org wrote: >> diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c >> index abe488bac50b..bf8d4d6f21f7 100644 >> --- a/tools/testing/selftests/net/tun.c >> +++ b/tools/testing/selftests/net/tun.c >> @@ -8,13 +8,19 @@ >> #include <stdlib.h> >> #include <string.h> >> #include <unistd.h> >> +#include <linux/filter.h> >> #include <linux/if_tun.h> >> #include <sys/ioctl.h> >> +#include <sys/mman.h> >> #include <sys/socket.h> >> >> #include "kselftest_harness.h" >> #include "tuntap_helpers.h" >> >> +#ifndef ARRAY_SIZE >> +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) >> +#endif > > This isn't a bug, but kselftest.h already provides ARRAY_SIZE through > kselftest_harness.h. Is this #ifndef block needed, or can it be dropped? > > Looking at the include chain: kselftest_harness.h includes kselftest.h, > and kselftest.h defines ARRAY_SIZE at line 63-65. kselftest_harness.h also > defines it again at line 749. Other harness-based tests in the same > directory (tls.c, ip_local_port_range.c) use ARRAY_SIZE without defining > it themselves. > Yes, thank you. I will drop it in v3. > [ ... ] > > > --- > AI reviewed your patch. Please fix the bug or email reply why it's not a bug. > See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md > > CI run summary: https://github.com/kernel-patches/bpf/actions/runs/36552651982 ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests 2026-09-29 9:37 ` [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci @ 2026-09-30 2:48 ` Willem de Bruijn 2026-09-30 6:25 ` weirongguang 2026-10-01 4:39 ` netdev-bot+sashiko 2 siblings, 1 reply; 17+ messages in thread From: Willem de Bruijn @ 2026-09-30 2:48 UTC (permalink / raw) To: Rongguang Wei, netdev Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, Rongguang Wei Rongguang Wei wrote: > From: Rongguang Wei <weirongguang@kylinos.cn> > > reattach_filter_without_user_buffer() attaches a filter, makes the buffer > the program was copied from unreadable and then attaches the queue again, > which used to fail with -EFAULT. > > attach_filter_bad_len_keeps_descriptor() checks that a rejected > TUNATTACHFILTER leaves both the saved descriptor and the installed filter > alone. > > attach_filter_nofilter_flag() and detach_filter_clears_reattach() cover the > ways to attach a queue without a filter. > > Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> > +/* accept: return skb->len */ > +static const struct sock_filter filter_accept[] = { > + BPF_STMT(BPF_LD | BPF_W | BPF_LEN, 0), > + BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0), > + BPF_STMT(BPF_RET | BPF_A, 0), > +}; > + > +/* drop: return 0 */ > +static const struct sock_filter filter_drop[] = { > + BPF_STMT(BPF_RET | BPF_K, 0), > +}; Are both programs needed? One will do, right? > +static int tap_get_iff(int fd, short *flags) nit: tun_get_iff? Or even tun_get_iff_flags. Also, could just return flags if positive, no call-by-reference needed. > +{ > + struct ifreq ifr = { 0 }; > + > + if (ioctl(fd, TUNGETIFF, (void *)&ifr) < 0) > + return -1; > + > + *flags = ifr.ifr_flags; > + > + return 0; > +} > + > +/* A new queue attached with IFF_NOFILTER must not get the filter that is > + * configured on the device. > + */ > +TEST_F(tun, attach_filter_nofilter_flag) > +{ > + struct ifreq ifr = { 0 }; > + short flags = 0; > + int fd; > + > + ASSERT_EQ(filter_attach(self->fd, filter_drop, ARRAY_SIZE(filter_drop)), 0); > + > + fd = open("/dev/net/tun", O_RDWR); > + ASSERT_GE(fd, 0); > + > + strcpy(ifr.ifr_name, self->ifname); > + ifr.ifr_flags = IFF_TAP | IFF_MULTI_QUEUE | IFF_NOFILTER; > + EXPECT_GE(ioctl(fd, TUNSETIFF, (void *)&ifr), 0); > + > + EXPECT_EQ(tap_get_iff(fd, &flags), 0); > + EXPECT_NE(flags & IFF_NOFILTER, 0); nit: here and elsewhere: avoid the double negative? EXPECT_EQ(flags & IFF_NOFILTER, IFF_NOFILTER) > +/* A TUNATTACHFILTER with a bad length must not clobber the saved descriptor */ What is the descriptor in this context? Do you mean installed program? > +TEST_F(tun, attach_filter_bad_len_keeps_descriptor) > +{ > + struct sock_fprog gf = { 0 }; > + short flags = 0; > + > + ASSERT_EQ(filter_attach(self->fd, filter_accept, ARRAY_SIZE(filter_accept)), 0); > + > + errno = 0; > + EXPECT_EQ(filter_attach(self->fd, filter_accept, 0), -1); > + EXPECT_EQ(errno, EINVAL); > + > + EXPECT_EQ(filter_get(self->fd, &gf), 0); > + EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept)); > + > + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); > + EXPECT_EQ(flags & IFF_NOFILTER, 0); > +} > + > FIXTURE(tun_vnet_udptnl) > { > char ifname[IFNAMSIZ]; > -- > 2.25.1 > > > No virus found > Checked by Hillstone Network AntiVirus > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests 2026-09-30 2:48 ` Willem de Bruijn @ 2026-09-30 6:25 ` weirongguang 0 siblings, 0 replies; 17+ messages in thread From: weirongguang @ 2026-09-30 6:25 UTC (permalink / raw) To: Willem de Bruijn, Rongguang Wei, netdev Cc: jasowangio, andrew+netdev, davem, kuba on 2026/9/30 10:48, Willem de Bruijn wrote: > Rongguang Wei wrote: >> From: Rongguang Wei <weirongguang@kylinos.cn> >> >> reattach_filter_without_user_buffer() attaches a filter, makes the buffer >> the program was copied from unreadable and then attaches the queue again, >> which used to fail with -EFAULT. >> >> attach_filter_bad_len_keeps_descriptor() checks that a rejected >> TUNATTACHFILTER leaves both the saved descriptor and the installed filter >> alone. >> >> attach_filter_nofilter_flag() and detach_filter_clears_reattach() cover the >> ways to attach a queue without a filter. >> >> Signed-off-by: Rongguang Wei <weirongguang@kylinos.cn> > >> +/* accept: return skb->len */ >> +static const struct sock_filter filter_accept[] = { >> + BPF_STMT(BPF_LD | BPF_W | BPF_LEN, 0), >> + BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, 0), >> + BPF_STMT(BPF_RET | BPF_A, 0), >> +}; >> + >> +/* drop: return 0 */ >> +static const struct sock_filter filter_drop[] = { >> + BPF_STMT(BPF_RET | BPF_K, 0), >> +}; > > Are both programs needed? One will do, right? > Yes, I dropped filter_drop and use filter_accept. >> +static int tap_get_iff(int fd, short *flags) > > nit: tun_get_iff? Or even tun_get_iff_flags. > > Also, could just return flags if positive, no call-by-reference needed. > Rename to tun_get_iff_flags() now and returns the flags, the call sites like: flags = tun_get_iff_flags(fd); EXPECT_GE(flags, 0); >> +{ >> + struct ifreq ifr = { 0 }; >> + >> + if (ioctl(fd, TUNGETIFF, (void *)&ifr) < 0) >> + return -1; >> + >> + *flags = ifr.ifr_flags; >> + >> + return 0; >> +} >> + > >> +/* A new queue attached with IFF_NOFILTER must not get the filter that is >> + * configured on the device. >> + */ >> +TEST_F(tun, attach_filter_nofilter_flag) >> +{ >> + struct ifreq ifr = { 0 }; >> + short flags = 0; >> + int fd; >> + >> + ASSERT_EQ(filter_attach(self->fd, filter_drop, ARRAY_SIZE(filter_drop)), 0); >> + >> + fd = open("/dev/net/tun", O_RDWR); >> + ASSERT_GE(fd, 0); >> + >> + strcpy(ifr.ifr_name, self->ifname); >> + ifr.ifr_flags = IFF_TAP | IFF_MULTI_QUEUE | IFF_NOFILTER; >> + EXPECT_GE(ioctl(fd, TUNSETIFF, (void *)&ifr), 0); >> + >> + EXPECT_EQ(tap_get_iff(fd, &flags), 0); >> + EXPECT_NE(flags & IFF_NOFILTER, 0); > > nit: here and elsewhere: avoid the double negative? >> EXPECT_EQ(flags & IFF_NOFILTER, IFF_NOFILTER) Done, all three of them. > >> +/* A TUNATTACHFILTER with a bad length must not clobber the saved descriptor */ > > What is the descriptor in this context? Do you mean installed program? > Yes, the installed program. Thank you, I will send the series as v3, which also puts the kernel copy of the program before the corrected error check, as suggested in the CI review of patch 1. >> +TEST_F(tun, attach_filter_bad_len_keeps_descriptor) >> +{ >> + struct sock_fprog gf = { 0 }; >> + short flags = 0; >> + >> + ASSERT_EQ(filter_attach(self->fd, filter_accept, ARRAY_SIZE(filter_accept)), 0); >> + >> + errno = 0; >> + EXPECT_EQ(filter_attach(self->fd, filter_accept, 0), -1); >> + EXPECT_EQ(errno, EINVAL); >> + >> + EXPECT_EQ(filter_get(self->fd, &gf), 0); >> + EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept)); >> + >> + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); >> + EXPECT_EQ(flags & IFF_NOFILTER, 0); >> +} >> + >> FIXTURE(tun_vnet_udptnl) >> { >> char ifname[IFNAMSIZ]; >> -- >> 2.25.1 >> >> >> No virus found >> Checked by Hillstone Network AntiVirus >> ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests 2026-09-29 9:37 ` [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci 2026-09-30 2:48 ` Willem de Bruijn @ 2026-10-01 4:39 ` netdev-bot+sashiko 2 siblings, 0 replies; 17+ messages in thread From: netdev-bot+sashiko @ 2026-10-01 4:39 UTC (permalink / raw) To: clementwei90 Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba, weirongguang Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin. Low priority / nit picks for your consideration: - [Low] In reattach_filter_without_user_buffer() (tools/testing/selftests/net/tun.c:614-641), only the tun_attach()… - [Low] attach_filter_bad_len_keeps_descriptor() only checks "the installed filter" through IFF_NOFILTER on self->fd… - [Low] attach_filter_nofilter_flag() (tools/testing/selftests/net/tun.c:646-665) only checks that a queue… - [Low] In reattach_filter_without_user_buffer() the inline comment at tools/testing/selftests/net/tun.c:627 says "The process that called… -- selftests: net: add TAP socket filter attach tests This adds four TAP selftests to tools/testing/selftests/net/tun.c for TUNATTACHFILTER handling. They cover four cases: - re-attaching a queue after the user buffer that held the filter is made unreadable - attaching a queue with IFF_NOFILTER - attaching a queue after TUNDETACHFILTER - rejecting a zero-length filter > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > index abe488bac50bb..bf8d4d6f21f77 100644 > --- a/tools/testing/selftests/net/tun.c > +++ b/tools/testing/selftests/net/tun.c [ ... ] > @@ -542,6 +548,159 @@ TEST_F(tun, reattach_close_delete) [ ... ] > +TEST_F(tun, reattach_filter_without_user_buffer) > +{ > + struct sock_fprog gf = { 0 }; > + short flags = 0; > + void *prog; > + int ret; > + > + prog = filter_alloc(filter_accept, ARRAY_SIZE(filter_accept)); > + ASSERT_NE(prog, NULL); > + ASSERT_EQ(filter_attach(self->fd, prog, ARRAY_SIZE(filter_accept)), 0); > + > + EXPECT_EQ(tun_detach(self->fd, self->ifname), 0); > + > + /* The process that called TUNATTACHFILTER no longer maps this memory */ > + ASSERT_EQ(mprotect(prog, getpagesize(), PROT_NONE), 0); [Severity: Low] This isn't a bug, but is this comment accurate? mprotect(PROT_NONE) makes the page unreadable, but it stays mapped. It is only unmapped at the end of the test by munmap(). The header comment above the test says the mapping is "made unreadable", which matches the code. copy_from_user() faults the same way in both cases, so the test still works. > + > + ret = tun_attach(self->fd, self->ifname); > + EXPECT_EQ(ret, 0); > + > + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); > + EXPECT_EQ(flags & IFF_NOFILTER, 0); > + > + EXPECT_EQ(filter_get(self->fd, &gf), 0); > + EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept)); [Severity: Low] Can these last two checks tell whether tun_attach() actually re-installed the filter? The tun_detach() helper uses TUNSETQUEUE with IFF_DETACH_QUEUE, which ends up in __tun_detach(tfile, false). That path disables the queue but never calls sk_detach_filter(). The socket therefore keeps the filter from the first TUNATTACHFILTER: drivers/net/tun.c:__tun_detach() { ... } else { tun_disable_queue(tun, tfile); tun_napi_disable(tfile); } ... } TUNGETIFF reports IFF_NOFILTER only when sk_filter is NULL: drivers/net/tun.c:__tun_chr_ioctl() { ... if (!tfile->socket.sk->sk_filter) ifr.ifr_flags |= IFF_NOFILTER; ... } So the IFF_NOFILTER check passes whether or not this block in tun_attach() ran: drivers/net/tun.c:tun_attach() { ... if (!skip_filter && (tun->filter_attached == true)) { lock_sock(tfile->socket.sk); err = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk); ... } TUNGETFILTER returns tun->fprog, which is the saved user descriptor, not fprog_kern: drivers/net/tun.c:__tun_chr_ioctl() { ... case TUNGETFILTER: ... if (copy_to_user(argp, &tun->fprog, sizeof(tun->fprog))) ... } So gf.len == 3 doesn't show which instructions the queue is running. Only the ret == 0 check can tell a fixed kernel from a broken one. If the re-install block were skipped, or filter_attached were wrongly false, the rest of the test would still pass. The comment describes a queue "attached long after the filter was configured". The test, however, reuses a queue that already has a filter. Could the test also attach a new queue with TUNSETIFF and without IFF_NOFILTER? On that queue, fprog_kern is the only source of the filter. It could also overwrite the still-readable buffer, for example with filter_drop. That would cover the other hazard described in "tun: keep a kernel copy of the socket filter program". > + > + ASSERT_EQ(mprotect(prog, getpagesize(), PROT_READ | PROT_WRITE), 0); > + ASSERT_EQ(munmap(prog, getpagesize()), 0); > +} > + > +/* A new queue attached with IFF_NOFILTER must not get the filter that is > + * configured on the device. > + */ > +TEST_F(tun, attach_filter_nofilter_flag) > +{ > + struct ifreq ifr = { 0 }; > + short flags = 0; > + int fd; > + > + ASSERT_EQ(filter_attach(self->fd, filter_drop, ARRAY_SIZE(filter_drop)), 0); > + > + fd = open("/dev/net/tun", O_RDWR); > + ASSERT_GE(fd, 0); > + > + strcpy(ifr.ifr_name, self->ifname); > + ifr.ifr_flags = IFF_TAP | IFF_MULTI_QUEUE | IFF_NOFILTER; > + EXPECT_GE(ioctl(fd, TUNSETIFF, (void *)&ifr), 0); > + > + EXPECT_EQ(tap_get_iff(fd, &flags), 0); > + EXPECT_NE(flags & IFF_NOFILTER, 0); [Severity: Low] Is there a positive control for this test? tun_set_iff() passes the IFF_NOFILTER bit to tun_attach() as skip_filter: drivers/net/tun.c:tun_set_iff() { ... err = tun_attach(tun, file, ifr->ifr_flags & IFF_NOFILTER, ... } None of the new tests opens a new queue with TUNSETIFF and without IFF_NOFILTER after TUNATTACHFILTER, then checks that the device filter is installed on it. Both fixture queues are created in FIXTURE_SETUP, before any filter exists. Suppose filter_attached were never set, or the re-install branch in tun_attach() never ran. The new socket here would have no filter, and this check would still pass. Would it help to also open a queue without IFF_NOFILTER and check that IFF_NOFILTER is clear on it? > + > + close(fd); > +} [ ... ] > +/* A TUNATTACHFILTER with a bad length must not clobber the saved descriptor */ > +TEST_F(tun, attach_filter_bad_len_keeps_descriptor) > +{ > + struct sock_fprog gf = { 0 }; > + short flags = 0; > + > + ASSERT_EQ(filter_attach(self->fd, filter_accept, ARRAY_SIZE(filter_accept)), 0); > + > + errno = 0; > + EXPECT_EQ(filter_attach(self->fd, filter_accept, 0), -1); > + EXPECT_EQ(errno, EINVAL); > + > + EXPECT_EQ(filter_get(self->fd, &gf), 0); > + EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept)); > + > + EXPECT_EQ(tap_get_iff(self->fd, &flags), 0); > + EXPECT_EQ(flags & IFF_NOFILTER, 0); [Severity: Low] The commit message says this test checks that a rejected TUNATTACHFILTER "leaves both the saved descriptor and the installed filter alone". Can this IFF_NOFILTER check detect a lost filter? The check reads sk_filter on self->fd. A failed attach on queue 0 never touches sk_filter, even on the kernel before this series. In the old code, len=0 was copied into tun->fprog. tun_attach_filter() then failed at i=0. tun_detach_filter(tun, 0) detached no sockets, but it still cleared the device state: drivers/net/tun.c:tun_detach_filter() { ... tun->filter_attached = false; } The socket kept its filter, so this check passed even though the device had silently lost its re-attach state. On the old kernel, only the gf.len check fails. tun_attach() reads tun->filter_attached and tun->fprog_kern on later attaches. The test never attaches a queue again after the failed ioctl. A regression could clear filter_attached or free fprog_kern before the length check while keeping tun->fprog. That regression would pass this test. Could the test detach and re-attach the queue (or add a new one) after the failed TUNATTACHFILTER, then check IFF_NOFILTER? That would show the installed filter is really kept. > +} > + > FIXTURE(tun_vnet_udptnl) > { > char ifname[IFNAMSIZ]; -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260929093712.131096-1-clementwei90%40163.com ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-10-01 4:39 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-29 9:37 [PATCH net v2 0/4] tun: fix re-attaching the socket filter Rongguang Wei 2026-09-29 9:37 ` [PATCH v2 1/4] tun: fix inverted error check when re-attaching the filter Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci 2026-09-30 2:23 ` weirongguang 2026-10-01 4:39 ` netdev-bot+sashiko 2026-09-29 9:37 ` [PATCH v2 2/4] net: filter: add sk_attach_filter_kern() function Rongguang Wei 2026-09-30 2:43 ` Willem de Bruijn 2026-09-30 6:28 ` Rongguang Wei 2026-10-01 4:39 ` netdev-bot+sashiko 2026-09-29 9:37 ` [PATCH v2 3/4] tun: keep a kernel copy of the socket filter program Rongguang Wei 2026-10-01 4:39 ` netdev-bot+sashiko 2026-09-29 9:37 ` [PATCH v2 4/4] selftests: net: add TAP socket filter attach tests Rongguang Wei 2026-09-29 10:34 ` bot+bpf-ci 2026-09-30 2:39 ` weirongguang 2026-09-30 2:48 ` Willem de Bruijn 2026-09-30 6:25 ` weirongguang 2026-10-01 4:39 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox