Netdev List
 help / color / mirror / Atom feed
* [PATCH net v6 0/2] tun: fix re-attaching the socket filter
@ 2026-10-16  7:06 Rongguang Wei
  2026-10-16  7:06 ` [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program Rongguang Wei
  2026-10-16  7:06 ` [PATCH net v6 2/2] selftests: net: add TAP socket filter attach tests Rongguang Wei
  0 siblings, 2 replies; 6+ messages in thread
From: Rongguang Wei @ 2026-10-16  7:06 UTC (permalink / raw)
  To: netdev
  Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba,
	Rongguang Wei

From: Rongguang Wei <weirongguang@kylinos.cn>

This is v6 of the series that fixes attaching a queue to a TAP device which
re-installs the socket filter of a persistent device.

Patch 1 keeps a copy of the program in the kernel when it is configured,
instead of reading tun->fprog from user space again on every later attach,
and fixes the inverted error check in tun_attach(). The two changes are
one patch on purpose: with only the kernel copy every re-attach returns 0
without publishing the queue, and with only the check fixed a re-attach
that used to succeed without a filter starts to fail. IFF_NOFILTER keeps
working and is no longer needed as a workaround.

Patch 2 adds selftests: it re-attaches a queue after the buffer the program
was copied from was made unreadable, attaches a second queue from a fresh
socket to check that the filter is rebuilt from the kernel copy, and checks
that a rejected TUNATTACHFILTER keeps the saved program, plus the ways to
attach a queue without a filter.

Thanks to Willem de Bruijn for the reviews, and to the CI reviews for
the comments they raised.

Rongguang Wei (2):
  tun: keep a kernel copy of the socket filter program
  selftests: net: add TAP socket filter attach tests

---
v6:
  - add description for return value of sk_attach_filter_kern and No code
    change is involved.
v5:
  - add kernel-doc for sk_attach_filter_kern() describing what the missing
    original program means for SO_GET_FILTER and sock_diag
  - mention in the message that a rejected header now leaves the installed
    filter alone
  - fix the wording of the mprotect() comment and note what the IFF_NOFILTER
    and TUNGETFILTER checks in the re-attach test
  - https://lore.kernel.org/netdev/20261012021113.45939-1-clementwei90@163.com/
v4:
  - merge the kernel copy and the error check into one patch, so that no
    step of the series turns a re-attach into a silent no-op or into a new
    failure
  - charge the kernel copy to the caller's memory cgroup
  - selftests: check that the queue is attached again, and attach a new
    queue to see that the filter was installed from the kernel copy
  - https://lore.kernel.org/netdev/20261003063859.136895-1-clementwei90@163.com/
v3:
  - reorder: the kernel copy of the program comes before the corrected
    error check
  - add sk_attach_filter_kern() in the patch that first uses it
  - address the review of the selftests
  - https://lore.kernel.org/netdev/20260930080746.135017-1-clementwei90@163.com/
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
  - https://lore.kernel.org/netdev/20260929093712.131096-1-clementwei90@163.com/
v1:
  - https://lore.kernel.org/netdev/20260923025653.59348-1-clementwei90@163.com/
---
 drivers/net/tun.c                 |  56 ++++++++-
 include/linux/filter.h            |   1 +
 net/core/filter.c                 |  41 +++++++
 tools/testing/selftests/net/tun.c | 197 ++++++++++++++++++++++++++++++
 4 files changed, 290 insertions(+), 5 deletions(-)

base-commit: 0fcb05b72344f40806cbe810de8872f1698bf053
-- 
2.25.1


No virus found
		Checked by Hillstone Network AntiVirus


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program
  2026-10-16  7:06 [PATCH net v6 0/2] tun: fix re-attaching the socket filter Rongguang Wei
@ 2026-10-16  7:06 ` Rongguang Wei
  2026-10-20  7:38   ` netdev-bot+sashiko
  2026-10-16  7:06 ` [PATCH net v6 2/2] selftests: net: add TAP socket filter attach tests Rongguang Wei
  1 sibling, 1 reply; 6+ messages in thread
From: Rongguang Wei @ 2026-10-16  7:06 UTC (permalink / raw)
  To: netdev
  Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba,
	Rongguang Wei, Willem de Bruijn

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.

Rebuilding the filter from that pointer is not reliable. With the inverted
check below, a failed read (-EFAULT for an unmapped address, -EINVAL for
bytes that are not a valid classic BPF program) was not fatal: for a new
tfile err is overwritten by "err = 0", so the queue was attached without a
filter. And when the read succeeded, the early return meant the queue was
never published at all.

Keep the program in the kernel instead: tun->fprog_kern holds the
instructions, and each queue gets its own program built from it with the
new sk_attach_filter_kern(), the kernel memory counterpart of
sk_attach_filter(). 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.

Also fix the inverted check at the same time when tun_attach() returns
early when sk_attach_filter_kern() succeeds instead of when it fails.
Neither change works on its own: with only the kernel copy, every re-attach
returns 0 without publishing the queue; with only the check fixed, a
re-attach that used to succeed without installing a filter would start to
fail.

sk_attach_filter_kern() builds the program with bpf_prog_create(), which
does not keep an original program, so SO_GET_FILTER returns -EACCES and
sock_diag omits the filter for sockets that use it. tun sockets are not
exposed as file descriptors, so this is not user visible.

Because the length is checked in tun_copy_filter() before anything is
replaced, a TUNATTACHFILTER with a bad header now fails early and leaves
the previously attached filter in place; before this series the failure
happened on queue 0 and cleared filter_attached.

The inverted check was discovered by manual code inspection first [1], and
the review of v1 reported the other things.

[1] https://lore.kernel.org/netdev/20260923025653.59348-1-clementwei90@163.com/

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>
Reviewed-by: Willem de Bruijn <willemb@google.com>
---
 drivers/net/tun.c      | 56 ++++++++++++++++++++++++++++++++++++++----
 include/linux/filter.h |  1 +
 net/core/filter.c      | 41 +++++++++++++++++++++++++++++++
 3 files changed, 93 insertions(+), 5 deletions(-)

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 242899f7fd07..e493c4554d5d 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,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;
+
+	if (!fprog.len || fprog.len > BPF_MAXINSNS)
+		return -EINVAL;
+
+	insns = kmalloc_array(fprog.len, sizeof(struct sock_filter),
+			      GFP_KERNEL_ACCOUNT);
+	if (!insns)
+		return -ENOMEM;
+
+	if (copy_from_user(insns, fprog.filter,
+			   fprog.len * sizeof(struct sock_filter))) {
+		kfree(insns);
+		return -EFAULT;
+	}
+
+	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)
 {
 	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);
@@ -752,10 +788,11 @@ 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)
+		if (err)
 			goto out;
+		rollback_filter = true;
 	}
 
 	if (!tfile->detached &&
@@ -817,6 +854,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;
 }
 
@@ -2400,6 +2442,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)
@@ -3064,6 +3107,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;
 }
 
@@ -3075,7 +3121,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);
@@ -3423,8 +3469,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);
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..57bef64b72bc 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -1567,6 +1567,47 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
 }
 EXPORT_SYMBOL_GPL(sk_attach_filter);
 
+/**
+ * sk_attach_filter_kern - attach a classic BPF filter held in kernel memory
+ * @fprog: program to attach, with @fprog->filter pointing at kernel memory
+ * @sk: socket to attach the filter to
+ *
+ * Attach a copy of a classic BPF program that the kernel already holds, the
+ * way sk_attach_filter() does for one that still lives in user space. The
+ * caller must hold the socket lock; SOCK_FILTER_LOCKED is checked here.
+ *
+ * The program is built by bpf_prog_create(), which does not keep an original
+ * copy of the instructions. sk_get_filter() therefore returns -EACCES and
+ * sock_diag leaves the filter out for a socket that uses this helper. A
+ * classic BPF program cannot be duplicated with bpf_prog_copy(), so keeping
+ * the original would cost another allocation and a copy per attach.
+ *
+ * Return: 0 on success, -EPERM if @sk has SOCK_FILTER_LOCKED set, -EINVAL if
+ * the instructions are not a valid classic BPF program, and -ENOMEM on an
+ * allocation failure.
+ */
+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] 6+ messages in thread

* [PATCH net v6 2/2] selftests: net: add TAP socket filter attach tests
  2026-10-16  7:06 [PATCH net v6 0/2] tun: fix re-attaching the socket filter Rongguang Wei
  2026-10-16  7:06 ` [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program Rongguang Wei
@ 2026-10-16  7:06 ` Rongguang Wei
  2026-10-20  7:38   ` netdev-bot+sashiko
  1 sibling, 1 reply; 6+ messages in thread
From: Rongguang Wei @ 2026-10-16  7:06 UTC (permalink / raw)
  To: netdev
  Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, kuba,
	Rongguang Wei, Willem de Bruijn

From: Rongguang Wei <weirongguang@kylinos.cn>

reattach_filter_without_user_buffer() attaches a filter, makes the buffer
the program was copied from unreadable, detaches the queue and attaches it
again. It then attaches a second queue from a fresh socket, which starts
without a filter of its own, so a clear IFF_NOFILTER shows that the filter
was installed again from the kernel copy.

attach_filter_bad_len_keeps_program() checks that a rejected
TUNATTACHFILTER leaves the installed program and the filter state that
TUNGETIFF reports alone, and that a queue attached afterwards still gets
the filter.

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>
Reviewed-by: Willem de Bruijn <willemb@google.com>
---
 tools/testing/selftests/net/tun.c | 197 ++++++++++++++++++++++++++++++
 1 file changed, 197 insertions(+)

diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c
index afaa81c9bac5..a4993a631957 100644
--- a/tools/testing/selftests/net/tun.c
+++ b/tools/testing/selftests/net/tun.c
@@ -8,9 +8,11 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <linux/filter.h>
 #include <linux/if_tun.h>
 #include <netinet/tcp.h>
 #include <sys/ioctl.h>
+#include <sys/mman.h>
 #include <sys/socket.h>
 
 #include "kselftest_harness.h"
@@ -543,6 +545,201 @@ 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),
+};
+
+/* 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 tun_get_iff_flags(int fd)
+{
+	struct ifreq ifr = { 0 };
+
+	if (ioctl(fd, TUNGETIFF, (void *)&ifr) < 0)
+		return -1;
+
+	return ifr.ifr_flags;
+}
+
+/*
+ * 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, and a
+ * second queue is then attached from a fresh socket, which starts without a
+ * filter of its own.
+ */
+TEST_F(tun, reattach_filter_without_user_buffer)
+{
+	struct sock_fprog gf = { 0 };
+	struct ifreq ifr = { 0 };
+	short flags = 0;
+	void *prog;
+	int fd, 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);
+
+	/* This memory is no longer readable */
+	ASSERT_EQ(mprotect(prog, getpagesize(), PROT_NONE), 0);
+
+	ret = tun_attach(self->fd, self->ifname);
+	EXPECT_EQ(ret, 0);
+
+	flags = tun_get_iff_flags(self->fd);
+	EXPECT_GE(flags, 0);
+	/* The queue is attached again, not left disabled */
+	EXPECT_EQ(flags & IFF_DETACH_QUEUE, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, 0);
+
+	/* The two checks below document the expected state, they cannot
+	 * tell a reinstalled filter from no filter: tun_detach() leaves
+	 * sk_filter alone, and TUNGETFILTER only returns the header.
+	 */
+	EXPECT_EQ(filter_get(self->fd, &gf), 0);
+	EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept));
+
+	/* A new queue starts without a filter, so a clear IFF_NOFILTER shows
+	 * that the filter was installed again from the kernel copy.
+	 */
+	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;
+	EXPECT_GE(ioctl(fd, TUNSETIFF, (void *)&ifr), 0);
+
+	flags = tun_get_iff_flags(fd);
+	EXPECT_GE(flags, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, 0);
+
+	close(fd);
+
+	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_accept, ARRAY_SIZE(filter_accept)), 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);
+
+	flags = tun_get_iff_flags(fd);
+	EXPECT_GE(flags, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, IFF_NOFILTER);
+
+	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_accept, ARRAY_SIZE(filter_accept)), 0);
+	EXPECT_EQ(ioctl(self->fd, TUNDETACHFILTER, 0), 0);
+
+	flags = tun_get_iff_flags(self->fd);
+	EXPECT_GE(flags, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, IFF_NOFILTER);
+
+	EXPECT_EQ(tun_detach(self->fd, self->ifname), 0);
+	EXPECT_EQ(tun_attach(self->fd, self->ifname), 0);
+
+	flags = tun_get_iff_flags(self->fd);
+	EXPECT_GE(flags, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, IFF_NOFILTER);
+}
+
+/* A TUNATTACHFILTER with a bad length must leave the installed program and the
+ * filter state that TUNGETIFF reports alone.
+ */
+TEST_F(tun, attach_filter_bad_len_keeps_program)
+{
+	struct sock_fprog gf = { 0 };
+	struct ifreq ifr = { 0 };
+	short flags = 0;
+	int fd;
+
+	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));
+
+	flags = tun_get_iff_flags(self->fd);
+	EXPECT_GE(flags, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, 0);
+
+	/* A queue attached afterwards still gets the filter, so the rejected
+	 * ioctl left the saved program and filter_attached alone.
+	 */
+	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;
+	EXPECT_GE(ioctl(fd, TUNSETIFF, (void *)&ifr), 0);
+
+	flags = tun_get_iff_flags(fd);
+	EXPECT_GE(flags, 0);
+	EXPECT_EQ(flags & IFF_NOFILTER, 0);
+
+	close(fd);
+}
+
 FIXTURE(tun_vnet_gso)
 {
 	char ifname[IFNAMSIZ];
-- 
2.25.1


No virus found
		Checked by Hillstone Network AntiVirus


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program
  2026-10-16  7:06 ` [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program Rongguang Wei
@ 2026-10-20  7:38   ` netdev-bot+sashiko
  2026-10-20 11:49     ` Rongguang Wei
  0 siblings, 1 reply; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20  7:38 UTC (permalink / raw)
  To: clementwei90
  Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem,
	kuba, weirongguang, willemb

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 0 · Medium: 1 (2) · Low: 2

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 save a classic BPF program that does not
  pass validation, and every later queue attach then fails.
- [Low] The new kernel-doc for sk_attach_filter_kern()
  (net/core/filter.c:1582) justifies not keeping orig_prog with "A…
- [Low] The kernel-doc 'Return:' section of sk_attach_filter_kern()
  (net/core/filter.c:1585-1588) lists 0, -EPERM, -EINVAL and -ENOMEM as…

Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but a
  TUNATTACHFILTER that fails during the per-queue attach leaves the…
- [Medium] This problem wasn't introduced by this patch, but
  TUNDETACHFILTER calls tun_detach_filter(tun, tun->numqueues).

--

tun: keep a kernel copy of the socket filter program

This patch keeps a kernel copy, in tun->fprog_kern, of the classic BPF
program passed to TUNATTACHFILTER. It adds sk_attach_filter_kern(), which
builds a per-queue program from that copy. It also fixes the inverted error
check in tun_attach() that runs when the filter is re-attached on a
persistent device.

Link: https://lore.kernel.org/netdev/179027275318.2160803.4185895144088175048@kernel.org/

> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index 242899f7fd071..e493c4554d5df 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;
> +
> +	if (!fprog.len || fprog.len > BPF_MAXINSNS)
> +		return -EINVAL;

[ ... ]

> +	kfree(tun->fprog_kern.filter);
> +	tun->fprog_kern.len = fprog.len;
> +	tun->fprog_kern.filter = insns;
> +	tun->fprog = fprog;

[Severity: Medium]
Only the header is checked here. The instructions go into tun->fprog_kern
without being validated. Can TUNATTACHFILTER store a program that
bpf_check_classic() would reject, and still return 0?

The instructions are only checked in the per-queue loop in
tun_attach_filter():

	for (i = 0; i < tun->numqueues; i++) {
		...
		ret = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk);
		...
	}

	tun->filter_attached = true;

If numqueues is 0, the loop never runs, and filter_attached is set to true
for the invalid program.

numqueues == 0 seems reachable on a multiqueue TAP device. Disable every
queue with TUNSETQUEUE/IFF_DETACH_QUEUE. __tun_detach(tfile, false) lowers
numqueues but leaves tfile->tun set, so TUNATTACHFILTER can still be issued
from the fd of a disabled queue.

With the inverted check now fixed, tun_attach() then does:

		err = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk);
		release_sock(tfile->socket.sk);
		if (err)
			goto out;

Wouldn't every IFF_ATTACH_QUEUE then fail with -EINVAL? tun_set_queue()
always passes skip_filter=false. The same applies to every TUNSETIFF on the
persistent device without IFF_NOFILTER. Before this patch, a TUNSETIFF
attach of a new tfile in this state succeeded, just without a filter.

The commit message says:

  Neither change works on its own: with only the kernel copy, every re-attach
  returns 0 without publishing the queue; with only the check fixed, a
  re-attach that used to succeed without installing a filter would start to
  fail.

The early-failure note in the commit message only covers bad headers. Since
the program is now a fixed kernel copy, could it be validated once in
tun_copy_filter() before the old copy is replaced? For example, call
bpf_prog_create() and then bpf_prog_destroy().

[ ... ]

> @@ -3064,6 +3107,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 TUNDETACHFILTER calls
tun_detach_filter(tun, tun->numqueues). That only walks the active
tun->tfiles[] entries, not the queues on tun->disabled.

So a queue disabled with IFF_DETACH_QUEUE (__tun_detach(tfile, false))
keeps its socket filter. Once filter_attached is cleared, re-enabling that
queue with IFF_ATTACH_QUEUE goes through tun_attach(), which skips the
filter block:

	if (!skip_filter && (tun->filter_attached == true)) {

Nothing removes the existing sk_filter on that path. Does tun_net_xmit()
then keep applying the old filter even though TUNDETACHFILTER succeeded? If
the old program was a drop-all filter, it would keep dropping packets.

>  
> @@ -3075,7 +3121,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 a TUNATTACHFILTER that fails here leaves
the device in an inconsistent state.

By this point tun_copy_filter() has already freed the old fprog_kern and
overwritten tun->fprog and tun->fprog_kern. Suppose sk_attach_filter_kern()
fails at queue i. Invalid instructions always fail at i == 0, and -ENOMEM
can happen at any i. tun_detach_filter(tun, i) then detaches only queues
0..i-1 and clears fprog_kern and filter_attached.

Do queues i..numqueues-1 keep the previous socket filter? tun_net_xmit()
would keep running it:

	if (tfile->socket.sk->sk_filter) {
		drop_reason = sk_filter_reason(tfile->socket.sk, skb);

Queues attached afterwards would get no filter. TUNGETFILTER would return
the header of the program that failed.

The patch only moves the length check earlier, so this can still happen
with bad instructions or allocation failures. Validating the instructions
in tun_copy_filter(), as asked above, would remove the bad-instruction
case.

[ ... ]

> diff --git a/net/core/filter.c b/net/core/filter.c
> index 70dc621672f2e..57bef64b72bcc 100644
> --- a/net/core/filter.c
> +++ b/net/core/filter.c
> @@ -1567,6 +1567,47 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
>  }
>  EXPORT_SYMBOL_GPL(sk_attach_filter);
>  
> +/**
> + * sk_attach_filter_kern - attach a classic BPF filter held in kernel memory
> + * @fprog: program to attach, with @fprog->filter pointing at kernel memory
> + * @sk: socket to attach the filter to
> + *
> + * Attach a copy of a classic BPF program that the kernel already holds, the
> + * way sk_attach_filter() does for one that still lives in user space. The
> + * caller must hold the socket lock; SOCK_FILTER_LOCKED is checked here.
> + *
> + * The program is built by bpf_prog_create(), which does not keep an original
> + * copy of the instructions. sk_get_filter() therefore returns -EACCES and
> + * sock_diag leaves the filter out for a socket that uses this helper. A
> + * classic BPF program cannot be duplicated with bpf_prog_copy(), so keeping

[Severity: Low]
This isn't a bug, but is bpf_prog_copy() the intended name here? No
function by that name seems to exist in the tree. This comment is the only
place it appears.

> + * the original would cost another allocation and a copy per attach.
> + *
> + * Return: 0 on success, -EPERM if @sk has SOCK_FILTER_LOCKED set, -EINVAL if
> + * the instructions are not a valid classic BPF program, and -ENOMEM on an
> + * allocation failure.
> + */

[Severity: Low]
Is this list of return values complete? Any error from bpf_prog_create() is
passed through unchanged, including errors from runtime selection:

sk_attach_filter_kern()
  bpf_prog_create()
    bpf_prepare_filter()
      bpf_migrate_filter()
        bpf_prog_select_runtime()

__bpf_prog_select_runtime() sets -ENOTSUPP when a JIT is required but no
JIT image was produced, for example with CONFIG_BPF_JIT_ALWAYS_ON:

		if (!fp->jited && jit_needed) {
			*err = -ENOTSUPP;
			return fp;

It also passes through whatever bpf_prog_lock_ro() returns. Userspace can
see these codes through TUNATTACHFILTER and the tun_attach() re-attach
paths.

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016070654.170932-1-clementwei90%40163.com

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v6 2/2] selftests: net: add TAP socket filter attach tests
  2026-10-16  7:06 ` [PATCH net v6 2/2] selftests: net: add TAP socket filter attach tests Rongguang Wei
@ 2026-10-20  7:38   ` netdev-bot+sashiko
  0 siblings, 0 replies; 6+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20  7:38 UTC (permalink / raw)
  To: clementwei90
  Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem,
	kuba, weirongguang, willemb

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] The commit message says attach_filter_bad_len_keeps_program()
  checks that "a rejected TUNATTACHFILTER leaves the installed program…

--

selftests: net: add TAP socket filter attach tests

This adds four TAP socket filter tests to the tun selftest. The first
re-attaches a queue after the user buffer holding the program is made
unreadable. The others cover attaching with IFF_NOFILTER, re-attaching
after TUNDETACHFILTER, and a TUNATTACHFILTER rejected for a bad length.

> diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c
> index afaa81c9bac53..a4993a6319572 100644
> --- a/tools/testing/selftests/net/tun.c
> +++ b/tools/testing/selftests/net/tun.c

[ ... ]

> +/* A TUNATTACHFILTER with a bad length must leave the installed program and the
> + * filter state that TUNGETIFF reports alone.
> + */
> +TEST_F(tun, attach_filter_bad_len_keeps_program)
> +{
> +	struct sock_fprog gf = { 0 };
> +	struct ifreq ifr = { 0 };
> +	short flags = 0;
> +	int fd;
> +
> +	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);

[Severity: Low]

The commit message says:

  attach_filter_bad_len_keeps_program() checks that a rejected
  TUNATTACHFILTER leaves the installed program and the filter state that
  TUNGETIFF reports alone, and that a queue attached afterwards still gets
  the filter.

Does the test cover that much? The only rejection it exercises is
len == 0. tun_copy_filter() rejects that case before it changes any state:

drivers/net/tun.c:tun_copy_filter() {
    ...
	if (!fprog.len || fprog.len > BPF_MAXINSNS)
		return -EINVAL;
    ...
}

Now take a valid length with instructions that are not a valid classic BPF
program. In that case tun_copy_filter() replaces the saved program before
anything validates it:

	kfree(tun->fprog_kern.filter);
	tun->fprog_kern.len = fprog.len;
	tun->fprog_kern.filter = insns;
	tun->fprog = fprog;

sk_attach_filter_kern() then fails on queue 0, so tun_attach_filter()
calls tun_detach_filter(tun, 0). That call detaches no sockets, but it
does:

	kfree(tun->fprog_kern.filter);
	tun->fprog_kern.filter = NULL;
	tun->fprog_kern.len = 0;
	tun->filter_attached = false;

So after this kind of rejection:

  - the existing queues keep the old sk_filter, so TUNGETIFF still shows
    IFF_NOFILTER clear
  - TUNGETFILTER copies tun->fprog, so it returns the rejected program's
    length, not ARRAY_SIZE(filter_accept)
  - a queue attached later with TUNSETIFF gets no filter, because
    filter_attached is false

Two of the three properties in the commit message don't hold on this
path, and no test covers it.

A related path is also untested. Suppose TUNATTACHFILTER with an invalid
program is issued while numqueues == 0. The loop in tun_attach_filter()
never runs, so the program is never validated, and filter_attached is set
to true. Every later drivers/net/tun.c:tun_attach() then fails with
-EINVAL from sk_attach_filter_kern().

This kernel behaviour was already there before this series, and the
companion kernel patch only claims to fix the bad header case. The test
name and its in-code comment are scoped to a bad length.

Could the changelog say "a TUNATTACHFILTER with a bad length" instead of
"a rejected TUNATTACHFILTER"? Alternatively, a case with a valid length and
invalid instructions could be added.

> +
> +	EXPECT_EQ(filter_get(self->fd, &gf), 0);
> +	EXPECT_EQ(gf.len, ARRAY_SIZE(filter_accept));

[ ... ]

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261016070654.170932-1-clementwei90%40163.com

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program
  2026-10-20  7:38   ` netdev-bot+sashiko
@ 2026-10-20 11:49     ` Rongguang Wei
  0 siblings, 0 replies; 6+ messages in thread
From: Rongguang Wei @ 2026-10-20 11:49 UTC (permalink / raw)
  To: netdev-bot+sashiko
  Cc: netdev, willemdebruijn.kernel, jasowangio, andrew+netdev, davem,
	kuba, weirongguang, willemb



on 2026/10/20 15:38, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 0 · Medium: 1 (2) · Low: 2
> 
> 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 save a classic BPF program that does not
>   pass validation, and every later queue attach then fails.
> - [Low] The new kernel-doc for sk_attach_filter_kern()
>   (net/core/filter.c:1582) justifies not keeping orig_prog with "A…
> - [Low] The kernel-doc 'Return:' section of sk_attach_filter_kern()
>   (net/core/filter.c:1585-1588) lists 0, -EPERM, -EINVAL and -ENOMEM as…
> 
> Pre-existing issues:
> - [Medium] This problem wasn't introduced by this patch, but a
>   TUNATTACHFILTER that fails during the per-queue attach leaves the…
> - [Medium] This problem wasn't introduced by this patch, but
>   TUNDETACHFILTER calls tun_detach_filter(tun, tun->numqueues).
> 
> --
> 
> tun: keep a kernel copy of the socket filter program
> 
> This patch keeps a kernel copy, in tun->fprog_kern, of the classic BPF
> program passed to TUNATTACHFILTER. It adds sk_attach_filter_kern(), which
> builds a per-queue program from that copy. It also fixes the inverted error
> check in tun_attach() that runs when the filter is re-attached on a
> persistent device.
> 
> Link: https://lore.kernel.org/netdev/179027275318.2160803.4185895144088175048@kernel.org/
> 
>> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
>> index 242899f7fd071..e493c4554d5df 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;
>> +
>> +	if (!fprog.len || fprog.len > BPF_MAXINSNS)
>> +		return -EINVAL;
> 
> [ ... ]
> 
>> +	kfree(tun->fprog_kern.filter);
>> +	tun->fprog_kern.len = fprog.len;
>> +	tun->fprog_kern.filter = insns;
>> +	tun->fprog = fprog;
> 
> [Severity: Medium]
> Only the header is checked here. The instructions go into tun->fprog_kern
> without being validated. Can TUNATTACHFILTER store a program that
> bpf_check_classic() would reject, and still return 0?
> 
> The instructions are only checked in the per-queue loop in
> tun_attach_filter():
> 
> 	for (i = 0; i < tun->numqueues; i++) {
> 		...
> 		ret = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk);
> 		...
> 	}
> 
> 	tun->filter_attached = true;
> 
> If numqueues is 0, the loop never runs, and filter_attached is set to true
> for the invalid program.
> 
> numqueues == 0 seems reachable on a multiqueue TAP device. Disable every
> queue with TUNSETQUEUE/IFF_DETACH_QUEUE. __tun_detach(tfile, false) lowers
> numqueues but leaves tfile->tun set, so TUNATTACHFILTER can still be issued
> from the fd of a disabled queue.
> 
> With the inverted check now fixed, tun_attach() then does:
> 
> 		err = sk_attach_filter_kern(&tun->fprog_kern, tfile->socket.sk);
> 		release_sock(tfile->socket.sk);
> 		if (err)
> 			goto out;
> 
> Wouldn't every IFF_ATTACH_QUEUE then fail with -EINVAL? tun_set_queue()
> always passes skip_filter=false. The same applies to every TUNSETIFF on the
> persistent device without IFF_NOFILTER. Before this patch, a TUNSETIFF
> attach of a new tfile in this state succeeded, just without a filter.
> 
> The commit message says:
> 
>   Neither change works on its own: with only the kernel copy, every re-attach
>   returns 0 without publishing the queue; with only the check fixed, a
>   re-attach that used to succeed without installing a filter would start to
>   fail.
> 
> The early-failure note in the commit message only covers bad headers. Since
> the program is now a fixed kernel copy, could it be validated once in
> tun_copy_filter() before the old copy is replaced? For example, call
> bpf_prog_create() and then bpf_prog_destroy().
> 
OK. I folded the fix into v7. Add a new bpf_prog_validate_classic(), 
which runs the checks of bpf_check_classic() on the copy without allocating 
a struct bpf_prog.
> [ ... ]
> 
>> @@ -3064,6 +3107,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 TUNDETACHFILTER calls
> tun_detach_filter(tun, tun->numqueues). That only walks the active
> tun->tfiles[] entries, not the queues on tun->disabled.
> 
> So a queue disabled with IFF_DETACH_QUEUE (__tun_detach(tfile, false))
> keeps its socket filter. Once filter_attached is cleared, re-enabling that
> queue with IFF_ATTACH_QUEUE goes through tun_attach(), which skips the
> filter block:
> 
> 	if (!skip_filter && (tun->filter_attached == true)) {
> 
> Nothing removes the existing sk_filter on that path. Does tun_net_xmit()
> then keep applying the old filter even though TUNDETACHFILTER succeeded? If
> the old program was a drop-all filter, it would keep dropping packets.
> 
>>  
>> @@ -3075,7 +3121,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 a TUNATTACHFILTER that fails here leaves
> the device in an inconsistent state.
> 
> By this point tun_copy_filter() has already freed the old fprog_kern and
> overwritten tun->fprog and tun->fprog_kern. Suppose sk_attach_filter_kern()
> fails at queue i. Invalid instructions always fail at i == 0, and -ENOMEM
> can happen at any i. tun_detach_filter(tun, i) then detaches only queues
> 0..i-1 and clears fprog_kern and filter_attached.
> 
> Do queues i..numqueues-1 keep the previous socket filter? tun_net_xmit()
> would keep running it:
> 
> 	if (tfile->socket.sk->sk_filter) {
> 		drop_reason = sk_filter_reason(tfile->socket.sk, skb);
> 
> Queues attached afterwards would get no filter. TUNGETFILTER would return
> the header of the program that failed.
> 
> The patch only moves the length check earlier, so this can still happen
> with bad instructions or allocation failures. Validating the instructions
> in tun_copy_filter(), as asked above, would remove the bad-instruction
> case.
> 
> [ ... ]
> 
>> diff --git a/net/core/filter.c b/net/core/filter.c
>> index 70dc621672f2e..57bef64b72bcc 100644
>> --- a/net/core/filter.c
>> +++ b/net/core/filter.c
>> @@ -1567,6 +1567,47 @@ int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
>>  }
>>  EXPORT_SYMBOL_GPL(sk_attach_filter);
>>  
>> +/**
>> + * sk_attach_filter_kern - attach a classic BPF filter held in kernel memory
>> + * @fprog: program to attach, with @fprog->filter pointing at kernel memory
>> + * @sk: socket to attach the filter to
>> + *
>> + * Attach a copy of a classic BPF program that the kernel already holds, the
>> + * way sk_attach_filter() does for one that still lives in user space. The
>> + * caller must hold the socket lock; SOCK_FILTER_LOCKED is checked here.
>> + *
>> + * The program is built by bpf_prog_create(), which does not keep an original
>> + * copy of the instructions. sk_get_filter() therefore returns -EACCES and
>> + * sock_diag leaves the filter out for a socket that uses this helper. A
>> + * classic BPF program cannot be duplicated with bpf_prog_copy(), so keeping
> 
> [Severity: Low]
> This isn't a bug, but is bpf_prog_copy() the intended name here? No
> function by that name seems to exist in the tree. This comment is the only
> place it appears.
> 
>> + * the original would cost another allocation and a copy per attach.
>> + *
>> + * Return: 0 on success, -EPERM if @sk has SOCK_FILTER_LOCKED set, -EINVAL if
>> + * the instructions are not a valid classic BPF program, and -ENOMEM on an
>> + * allocation failure.
>> + */
> 
> [Severity: Low]
> Is this list of return values complete? Any error from bpf_prog_create() is
> passed through unchanged, including errors from runtime selection:
> 
> sk_attach_filter_kern()
>   bpf_prog_create()
>     bpf_prepare_filter()
>       bpf_migrate_filter()
>         bpf_prog_select_runtime()
> 
> __bpf_prog_select_runtime() sets -ENOTSUPP when a JIT is required but no
> JIT image was produced, for example with CONFIG_BPF_JIT_ALWAYS_ON:
> 
> 		if (!fp->jited && jit_needed) {
> 			*err = -ENOTSUPP;
> 			return fp;
> 
> It also passes through whatever bpf_prog_lock_ro() returns. Userspace can
> see these codes through TUNATTACHFILTER and the tun_attach() re-attach
> paths.
> 
> [ ... ]
> 
pw-bot: cr


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-10-20 11:50 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16  7:06 [PATCH net v6 0/2] tun: fix re-attaching the socket filter Rongguang Wei
2026-10-16  7:06 ` [PATCH net v6 1/2] tun: keep a kernel copy of the socket filter program Rongguang Wei
2026-10-20  7:38   ` netdev-bot+sashiko
2026-10-20 11:49     ` Rongguang Wei
2026-10-16  7:06 ` [PATCH net v6 2/2] selftests: net: add TAP socket filter attach tests Rongguang Wei
2026-10-20  7:38   ` 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