From d8a4f4c59da52b7c31ae337243b8473a19a3f819 Mon Sep 17 00:00:00 2001 From: Prasad Koya Date: Mon, 26 Oct 2026 19:46:35 -0700 Subject: [PATCH] macvlan: fix allmulti reference counting in passthru mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enabling allmulticast on a down passthru macvlan, bringing it up, then toggling allmulticast off and on can underflow the lower device's allmulti reference count. macvlan_change_rx_flags() only propagates IFF_ALLMULTI changes while the macvlan is up. However, the passthru path in macvlan_open() skips taking an allmulti reference when IFF_ALLMULTI is already set. Clearing allmulticast after opening the device therefore decrements a reference that was never taken. With no other allmulti users, the unsigned counter wraps from zero to UINT_MAX. The next increment fails with -EOVERFLOW and reports "allmulti touches roof". The issue was observed during development testing with 5.10 and 6.12 kernels. It was reproduced on Linux 6.12.68 by enabling allmulticast on a down passthru macvlan, bringing it up, and toggling allmulticast off and on. This triggered the “allmulti touches roof” warning. Signed-off-by: Prasad Koya Signed-off-by: Dave Miller --- drivers/net/macvlan.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/macvlan.c b/drivers/net/macvlan.c index a48c9e662fe9..4f8416bb5169 100644 --- a/drivers/net/macvlan.c +++ b/drivers/net/macvlan.c @@ -643,6 +643,14 @@ static int macvlan_open(struct net_device *dev) if (err < 0) goto out; } + if (dev->flags & IFF_ALLMULTI) { + err = dev_set_allmulti(lowerdev, 1); + if (err < 0) { + if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) + dev_set_promiscuity(lowerdev, -1); + goto out; + } + } goto hash_add; } @@ -713,6 +721,8 @@ static int macvlan_stop(struct net_device *dev) dev_mc_unsync(lowerdev, dev); if (macvlan_passthru(vlan->port)) { + if (dev->flags & IFF_ALLMULTI) + dev_set_allmulti(lowerdev, -1); if (!(vlan->flags & MACVLAN_FLAG_NOPROMISC)) dev_set_promiscuity(lowerdev, -1); goto hash_del;