Netdev List
 help / color / mirror / Atom feed
* [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes
@ 2026-08-28  8:45 Eric Dumazet
  2026-08-28  8:45 ` [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() Eric Dumazet
                   ` (5 more replies)
  0 siblings, 6 replies; 12+ messages in thread
From: Eric Dumazet @ 2026-08-28  8:45 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Ido Schimmel, David Ahern, Simon Horman, netdev, eric.dumazet,
	Eric Dumazet

This series addresses several RCU synchronization and timer calculation
issues identified in IPv6 multicast (MLD) handling within net/ipv6/mcast.c
while I was working on fixing a syzbot report in net/ipv4/icmp.c.

Patch 1 fixes an RCU reader diversion in ip6_mc_del1_src() where mutating
psf->sf_next to insert an unlinked source node into the tombstone list
diverted concurrent lockless readers (e.g. ipv6_chk_mcast_addr()) into
pmc->mca_tomb, causing them to miss remaining active sources.

Patch 2 converts ip6_mc_source() to use copy-on-write RCU updates.
Previously, source additions and deletions modified the socket's
psl->sl_addr array in-place, causing concurrent lockless readers in
inet6_mc_check() (UDP/RAW receive path) to observe torn 16-byte IPv6
addresses or duplicated/missed sources.

Patch 3 fixes delay calculation in igmp6_join_group() when canceling an
existing delayed work, preventing unsigned jiffies underflows when the
timer has already expired and clamping the delay to the unsolicited report
interval.

Patch 4 ensures rcu_assign_pointer() is consistently used for __rcu list
updates in __ipv6_dev_mc_dec(), ipv6_sock_mc_drop(), __ipv6_sock_mc_close(),
and related helpers.

Patch 5 switches igmp6_mc_seq_show() to use jiffies_delta_to_clock_t()
with a signed long delta, preventing underflows in /proc/net/igmp6 timer
duration reporting.

v2: addressed AI review.
v1: https://lore.kernel.org/netdev/20260826103711.3302915-1-edumazet@google.com/

Eric Dumazet (5):
  ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src()
  ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source()
  ipv6: mcast: fix delay calculation in igmp6_join_group()
  ipv6: mcast: use rcu_assign_pointer() for __rcu list updates
  ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show()

 include/net/if_inet6.h |   2 -
 net/ipv6/mcast.c       | 148 ++++++++++++++++++++++++-----------------
 2 files changed, 88 insertions(+), 62 deletions(-)

-- 
2.55.0.897.gb25b4bd76c-goog


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

* [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src()
  2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
@ 2026-08-28  8:45 ` Eric Dumazet
  2026-08-30 15:44   ` Ido Schimmel
  2026-08-28  8:45 ` [PATCH v2 net 2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source() Eric Dumazet
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-08-28  8:45 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Ido Schimmel, David Ahern, Simon Horman, netdev, eric.dumazet,
	Eric Dumazet, Taehee Yoo

When removing a source filter whose count reaches zero, ip6_mc_del1_src()
unlinks psf from pmc->mca_sources. If the filter was previously active,
the code moved psf directly into pmc->mca_tomb by updating psf->sf_next.

Because pmc->mca_sources is traversed locklessly under RCU (e.g. by
ipv6_chk_mcast_addr()), mutating psf->sf_next before a grace period
elapses diverts concurrent readers to the tombstone list. Consequently,
readers miss remaining active sources in pmc->mca_sources and improperly
examine deleted tombstone entries.

Fix this by allocating a new tombstone node for pmc->mca_tomb (as done
in sf_setstate()) and retiring the original psf via kfree_rcu().

Fixes: 4b200e398953 ("mld: convert ip6_sf_list to RCU")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Taehee Yoo <ap420073@gmail.com>
---
 net/ipv6/mcast.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index aaba4c2aae23ef378e6328e6eee880ee1e52a76b..ec7fac511c8d50da7125ff1c4cd23af46875791d 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -2351,14 +2351,18 @@ static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode,
 
 		if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) &&
 		    !mld_in_v1_mode(idev)) {
-			psf->sf_crcount = idev->mc_qrv;
-			rcu_assign_pointer(psf->sf_next,
-					   mc_dereference(pmc->mca_tomb, idev));
-			rcu_assign_pointer(pmc->mca_tomb, psf);
-			rv = 1;
-		} else {
-			kfree_rcu(psf, rcu);
+			struct ip6_sf_list *dpsf = kmalloc_obj(*dpsf);
+
+			if (dpsf) {
+				*dpsf = *psf;
+				dpsf->sf_crcount = idev->mc_qrv;
+				rcu_assign_pointer(dpsf->sf_next,
+						   mc_dereference(pmc->mca_tomb, idev));
+				rcu_assign_pointer(pmc->mca_tomb, dpsf);
+				rv = 1;
+			}
 		}
+		kfree_rcu(psf, rcu);
 	}
 	return rv;
 }
-- 
2.55.0.897.gb25b4bd76c-goog


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

* [PATCH v2 net 2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source()
  2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
  2026-08-28  8:45 ` [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() Eric Dumazet
@ 2026-08-28  8:45 ` Eric Dumazet
  2026-08-30 15:44   ` Ido Schimmel
  2026-08-28  8:45 ` [PATCH v2 net 3/5] ipv6: mcast: fix delay calculation in igmp6_join_group() Eric Dumazet
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-08-28  8:45 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Ido Schimmel, David Ahern, Simon Horman, netdev, eric.dumazet,
	Eric Dumazet, Taehee Yoo

pmc->sflist is read locklessly under rcu_read_lock() by
inet6_mc_check() during packet reception in the UDP and RAW
multicast receive paths.

ip6_mc_source() mutated psl->sl_addr and psl->sl_count in-place
when adding or removing a source filter. Additionally, when expanding
the filter buffer, newpsl was published via rcu_assign_pointer()
before writing the new source into the array.

Because 16-byte struct in6_addr writes are not atomic and array
shifting is not synchronized with RCU readers, concurrent readers in
inet6_mc_check() could read torn IPv6 addresses or observe
duplicated/missed source entries.

Fix this by switching ip6_mc_source() to copy-on-write RCU updates:
allocate and fully populate newpsl before publishing it via
rcu_assign_pointer(), and reclaim the old filter via kfree_rcu(),
matching ip6_mc_msfilter().

Also remove the now unused IP6_SFBLOCK macro.

Fixes: 882ba1f73c06 ("mld: convert ipv6_mc_socklist->sflist to RCU")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Taehee Yoo <ap420073@gmail.com>
---
v2: See https://lore.kernel.org/netdev/CANn89i++X6UUpACK+Bq9d+qbiu1NQ=Ynz75OQgbWjUHNyu-S8Q@mail.gmail.com/

 include/net/if_inet6.h |  2 -
 net/ipv6/mcast.c       | 98 ++++++++++++++++++++++++------------------
 2 files changed, 56 insertions(+), 44 deletions(-)

diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h
index 238ad3349456a3afeab6c02172a9e2d681d2ec9c..795fb41b45f5d10d903de220df3cd0c3798561a0 100644
--- a/include/net/if_inet6.h
+++ b/include/net/if_inet6.h
@@ -88,8 +88,6 @@ struct ip6_sf_socklist {
 	struct in6_addr		sl_addr[] __counted_by(sl_max);
 };
 
-#define IP6_SFBLOCK	10	/* allocate this many at once */
-
 struct ipv6_mc_socklist {
 	struct in6_addr		addr;
 	int			ifindex;
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index ec7fac511c8d50da7125ff1c4cd23af46875791d..66f5858e5fea68e27da1f857b5596808632618a3 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -355,12 +355,12 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
 {
 	struct ipv6_pinfo *inet6 = inet6_sk(sk);
 	struct in6_addr *source, *group;
+	struct ip6_sf_socklist *newpsl, *psl;
 	struct net *net = sock_net(sk);
 	struct ipv6_mc_socklist *pmc;
-	struct ip6_sf_socklist *psl;
 	struct inet6_dev *idev;
 	int leavegroup = 0;
-	int i, j, rv;
+	int i, j;
 	int err;
 
 	source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr;
@@ -409,13 +409,11 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
 	if (!add) {
 		if (!psl)
 			goto done;	/* err = -EADDRNOTAVAIL */
-		rv = !0;
 		for (i = 0; i < psl->sl_count; i++) {
-			rv = !ipv6_addr_equal(&psl->sl_addr[i], source);
-			if (rv == 0)
+			if (ipv6_addr_equal(&psl->sl_addr[i], source))
 				break;
 		}
-		if (rv)		/* source not found */
+		if (i == psl->sl_count)		/* source not found */
 			goto done;	/* err = -EADDRNOTAVAIL */
 
 		/* special case - (INCLUDE, empty) == LEAVE_GROUP */
@@ -424,58 +422,74 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
 			goto done;
 		}
 
+		atomic_sub(struct_size(psl, sl_addr, psl->sl_max),
+			   &sk->sk_omem_alloc);
+
+		if (psl->sl_count == 1) {
+			newpsl = NULL;
+		} else {
+			newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr,
+							      psl->sl_count - 1),
+					      GFP_KERNEL);
+			if (!newpsl) {
+				atomic_add(struct_size(psl, sl_addr, psl->sl_max),
+					   &sk->sk_omem_alloc);
+				err = -ENOBUFS;
+				goto done;
+			}
+			newpsl->sl_max = psl->sl_count - 1;
+			newpsl->sl_count = psl->sl_count - 1;
+			for (j = 0; j < i; j++)
+				newpsl->sl_addr[j] = psl->sl_addr[j];
+			for (j = i + 1; j < psl->sl_count; j++)
+				newpsl->sl_addr[j - 1] = psl->sl_addr[j];
+		}
+
 		/* update the interface filter */
 		ip6_mc_del_src(idev, group, omode, 1, source, 1);
 
-		for (j = i+1; j < psl->sl_count; j++)
-			psl->sl_addr[j-1] = psl->sl_addr[j];
-		psl->sl_count--;
+		rcu_assign_pointer(pmc->sflist, newpsl);
+		kfree_rcu(psl, rcu);
 		err = 0;
 		goto done;
 	}
 	/* else, add a new source to the filter */
 
-	if (psl && psl->sl_count >= sysctl_mld_max_msf) {
+	if (psl && psl->sl_count >= READ_ONCE(sysctl_mld_max_msf)) {
 		err = -ENOBUFS;
 		goto done;
 	}
-	if (!psl || psl->sl_count == psl->sl_max) {
-		struct ip6_sf_socklist *newpsl;
-		int count = IP6_SFBLOCK;
-
-		if (psl)
-			count += psl->sl_max;
-		newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, count),
-				      GFP_KERNEL);
-		if (!newpsl) {
-			err = -ENOBUFS;
-			goto done;
-		}
-		newpsl->sl_max = count;
-		newpsl->sl_count = count - IP6_SFBLOCK;
-		if (psl) {
-			for (i = 0; i < psl->sl_count; i++)
-				newpsl->sl_addr[i] = psl->sl_addr[i];
-			atomic_sub(struct_size(psl, sl_addr, psl->sl_max),
-				   &sk->sk_omem_alloc);
+	if (psl) {
+		for (i = 0; i < psl->sl_count; i++) {
+			if (ipv6_addr_equal(&psl->sl_addr[i], source))
+				goto done; /* err = -EADDRNOTAVAIL */
 		}
-		rcu_assign_pointer(pmc->sflist, newpsl);
-		kfree_rcu(psl, rcu);
-		psl = newpsl;
 	}
-	rv = 1;	/* > 0 for insert logic below if sl_count is 0 */
-	for (i = 0; i < psl->sl_count; i++) {
-		rv = !ipv6_addr_equal(&psl->sl_addr[i], source);
-		if (rv == 0) /* There is an error in the address. */
-			goto done;
+
+	i = psl ? psl->sl_count + 1 : 1;
+	newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr, i),
+			      GFP_KERNEL);
+	if (!newpsl) {
+		err = -ENOBUFS;
+		goto done;
 	}
-	for (j = psl->sl_count-1; j >= i; j--)
-		psl->sl_addr[j+1] = psl->sl_addr[j];
-	psl->sl_addr[i] = *source;
-	psl->sl_count++;
-	err = 0;
+	newpsl->sl_max = i;
+	newpsl->sl_count = i;
+	if (psl) {
+		for (j = 0; j < psl->sl_count; j++)
+			newpsl->sl_addr[j] = psl->sl_addr[j];
+	}
+	newpsl->sl_addr[i - 1] = *source;
+
 	/* update the interface list */
 	ip6_mc_add_src(idev, group, omode, 1, source, 1);
+
+	if (psl)
+		atomic_sub(struct_size(psl, sl_addr, psl->sl_max),
+			   &sk->sk_omem_alloc);
+	rcu_assign_pointer(pmc->sflist, newpsl);
+	kfree_rcu(psl, rcu);
+	err = 0;
 done:
 	mutex_unlock(&idev->mc_lock);
 	in6_dev_put(idev);
-- 
2.55.0.897.gb25b4bd76c-goog


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

* [PATCH v2 net 3/5] ipv6: mcast: fix delay calculation in igmp6_join_group()
  2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
  2026-08-28  8:45 ` [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() Eric Dumazet
  2026-08-28  8:45 ` [PATCH v2 net 2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source() Eric Dumazet
@ 2026-08-28  8:45 ` Eric Dumazet
  2026-08-30 15:44   ` Ido Schimmel
  2026-08-28  8:45 ` [PATCH v2 net 4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates Eric Dumazet
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-08-28  8:45 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Ido Schimmel, David Ahern, Simon Horman, netdev, eric.dumazet,
	Eric Dumazet, Taehee Yoo

When joining a multicast group, if a report work is already pending
(e.g. scheduled by a query or a previous join), igmp6_join_group()
cancels the delayed work and recalculates the delay:

	if (cancel_delayed_work(&ma->mca_work)) {
		refcount_dec(&ma->mca_refcnt);
		delay = ma->mca_work.timer.expires - jiffies;
	}

Unlike igmp6_group_queried(), igmp6_join_group() did not check
if delay >= interval. This leads to two issues:

1. If the timer has already expired (timer.expires <= jiffies), the
   stale expiry is reused by mod_delayed_work(), causing the second
   unsolicited report to fire on the very next tick without a
   randomized delay.
2. If the timer was originally armed by a query with a large
   maximum response delay, delay could exceed
   unsolicited_report_interval(ma->idev).

Fix this by initializing delay to unsolicited_report_interval(ma->idev)
and re-randomizing it with get_random_u32_below(interval) when
delay >= interval, mirroring the logic in igmp6_group_queried().

Fixes: 2d9a93b4902b ("mld: convert from timer to delayed work")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Taehee Yoo <ap420073@gmail.com>
---
v2: refined the changelog

 net/ipv6/mcast.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 66f5858e5fea68e27da1f857b5596808632618a3..4423b90dc9abffff9661da22caa4b9e2f618def7 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -2639,7 +2639,7 @@ static void ip6_mc_clear_src(struct ifmcaddr6 *pmc)
 
 static void igmp6_join_group(struct ifmcaddr6 *ma)
 {
-	unsigned long delay;
+	unsigned long delay, interval;
 
 	mc_assert_locked(ma->idev);
 
@@ -2648,13 +2648,17 @@ static void igmp6_join_group(struct ifmcaddr6 *ma)
 
 	igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT);
 
-	delay = get_random_u32_below(unsolicited_report_interval(ma->idev));
+	interval = unsolicited_report_interval(ma->idev);
+	delay = interval;
 
 	if (cancel_delayed_work(&ma->mca_work)) {
 		refcount_dec(&ma->mca_refcnt);
 		delay = ma->mca_work.timer.expires - jiffies;
 	}
 
+	if (delay >= interval)
+		delay = get_random_u32_below(interval);
+
 	if (!mod_delayed_work(mld_wq, &ma->mca_work, delay))
 		refcount_inc(&ma->mca_refcnt);
 	WRITE_ONCE(ma->mca_flags, ma->mca_flags |
-- 
2.55.0.897.gb25b4bd76c-goog


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

* [PATCH v2 net 4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates
  2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
                   ` (2 preceding siblings ...)
  2026-08-28  8:45 ` [PATCH v2 net 3/5] ipv6: mcast: fix delay calculation in igmp6_join_group() Eric Dumazet
@ 2026-08-28  8:45 ` Eric Dumazet
  2026-08-30 15:45   ` Ido Schimmel
  2026-08-28  8:45 ` [PATCH v2 net 5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show() Eric Dumazet
  2026-09-01  1:10 ` [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes patchwork-bot+netdevbpf
  5 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-08-28  8:45 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Ido Schimmel, David Ahern, Simon Horman, netdev, eric.dumazet,
	Eric Dumazet, Taehee Yoo

Several places in net/ipv6/mcast.c update RCU-protected lists
(np->ipv6_mc_list, idev->mc_list, idev->mc_tomb) using direct pointer
assignments instead of rcu_assign_pointer():

1. In __ipv6_dev_mc_dec(), unlinking a group from idev->mc_list did:
       *map = ma->next;
   without rcu_assign_pointer() while concurrent readers traverse
   idev->mc_list locklessly under rcu_read_lock().
2. In ipv6_sock_mc_drop() and __ipv6_sock_mc_close(), unlinking a group
   from np->ipv6_mc_list directly assigned *lnk = mc_lst->next and
   np->ipv6_mc_list = mc_lst->next without rcu_assign_pointer(), racing
   with lockless readers in inet6_mc_check().
3. In __ipv6_sock_mc_join(), mc_lst->next was initialized to
   np->ipv6_mc_list via raw assignment before publishing mc_lst.
4. In mld_del_delrec() and __ipv6_dev_mc_inc(), __rcu source pointers
   passed into rcu_assign_pointer() lacked explicit dereference helpers.

Fix these by consistently using rcu_assign_pointer() along with
mc_dereference() / sock_dereference().

Fixes: 456b61bca8ee ("ipv6: mcast: RCU conversion")
Fixes: 88e2ca308094 ("mld: convert ifmcaddr6 to RCU")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Taehee Yoo <ap420073@gmail.com>
---
 net/ipv6/mcast.c | 20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 4423b90dc9abffff9661da22caa4b9e2f618def7..2290457eb8d3dd7a8d19fa9bf19f97160b7d2492 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -240,7 +240,8 @@ static int __ipv6_sock_mc_join(struct sock *sk, int ifindex,
 		return err;
 	}
 
-	mc_lst->next = np->ipv6_mc_list;
+	rcu_assign_pointer(mc_lst->next,
+			   sock_dereference(np->ipv6_mc_list, sk));
 	rcu_assign_pointer(np->ipv6_mc_list, mc_lst);
 
 	return 0;
@@ -300,7 +301,8 @@ int ipv6_sock_mc_drop(struct sock *sk, int ifindex, const struct in6_addr *addr)
 	      lnk = &mc_lst->next) {
 		if ((ifindex == 0 || mc_lst->ifindex == ifindex) &&
 		    ipv6_addr_equal(&mc_lst->addr, addr)) {
-			*lnk = mc_lst->next;
+			rcu_assign_pointer(*lnk,
+					   sock_dereference(mc_lst->next, sk));
 			__ipv6_sock_mc_drop(sk, mc_lst);
 			return 0;
 		}
@@ -333,7 +335,8 @@ void __ipv6_sock_mc_close(struct sock *sk)
 	struct ipv6_mc_socklist *mc_lst;
 
 	while ((mc_lst = sock_dereference(np->ipv6_mc_list, sk)) != NULL) {
-		np->ipv6_mc_list = mc_lst->next;
+		rcu_assign_pointer(np->ipv6_mc_list,
+				   sock_dereference(mc_lst->next, sk));
 		__ipv6_sock_mc_drop(sk, mc_lst);
 	}
 }
@@ -798,9 +801,11 @@ static void mld_del_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im)
 	if (!pmc)
 		return;
 	if (pmc_prev)
-		rcu_assign_pointer(pmc_prev->next, pmc->next);
+		rcu_assign_pointer(pmc_prev->next,
+				   mc_dereference(pmc->next, idev));
 	else
-		rcu_assign_pointer(idev->mc_tomb, pmc->next);
+		rcu_assign_pointer(idev->mc_tomb,
+				   mc_dereference(pmc->next, idev));
 
 	im->idev = pmc->idev;
 	if (im->mca_sfmode == MCAST_INCLUDE) {
@@ -980,7 +985,7 @@ static int __ipv6_dev_mc_inc(struct net_device *dev,
 		return -ENOMEM;
 	}
 
-	rcu_assign_pointer(mc->next, idev->mc_list);
+	rcu_assign_pointer(mc->next, mc_dereference(idev->mc_list, idev));
 	rcu_assign_pointer(idev->mc_list, mc);
 
 	mld_del_delrec(idev, mc);
@@ -1014,7 +1019,8 @@ int __ipv6_dev_mc_dec(struct inet6_dev *idev, const struct in6_addr *addr)
 
 			WRITE_ONCE(ma->mca_users, new_users);
 			if (new_users == 0) {
-				*map = ma->next;
+				rcu_assign_pointer(*map,
+						   mc_dereference(ma->next, idev));
 
 				igmp6_group_dropped(ma);
 				inet6_ifmcaddr_notify(idev->dev, ma,
-- 
2.55.0.897.gb25b4bd76c-goog


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

* [PATCH v2 net 5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show()
  2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
                   ` (3 preceding siblings ...)
  2026-08-28  8:45 ` [PATCH v2 net 4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates Eric Dumazet
@ 2026-08-28  8:45 ` Eric Dumazet
  2026-08-30 15:45   ` Ido Schimmel
  2026-09-01  1:10 ` [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes patchwork-bot+netdevbpf
  5 siblings, 1 reply; 12+ messages in thread
From: Eric Dumazet @ 2026-08-28  8:45 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Ido Schimmel, David Ahern, Simon Horman, netdev, eric.dumazet,
	Eric Dumazet

If a multicast group timer has expired but the delayed work has
not yet run to clear MAF_TIMER_RUNNING, expires - jiffies produces
a negative value.

Because unsigned arithmetic was used with jiffies_to_clock_t(),
expires - jiffies underflows to a huge value and reports invalid
timer durations in /proc/net/igmp6.

Use jiffies_delta_to_clock_t() with a signed long delta to properly
cap expired deltas to 0, matching IPv4 igmp_mc_seq_show() and commit
a399a8053164 ("time: jiffies_delta_to_clock_t() helper to the rescue").

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 net/ipv6/mcast.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index 2290457eb8d3dd7a8d19fa9bf19f97160b7d2492..ecef55f261890c3de11c874d623d268dcb11670f 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -3029,7 +3029,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v)
 	struct ifmcaddr6 *im = (struct ifmcaddr6 *)v;
 	struct igmp6_mc_iter_state *state = igmp6_mc_seq_private(seq);
 	unsigned int mca_flags = READ_ONCE(im->mca_flags);
-	unsigned long expires = READ_ONCE(im->mca_work.timer.expires);
+	long delta = READ_ONCE(im->mca_work.timer.expires) - jiffies;
 
 	seq_printf(seq,
 		   "%-4d %-15s %pi6 %5d %08X %ld\n",
@@ -3037,7 +3037,7 @@ static int igmp6_mc_seq_show(struct seq_file *seq, void *v)
 		   &im->mca_addr,
 		   READ_ONCE(im->mca_users), mca_flags,
 		   (mca_flags & MAF_TIMER_RUNNING) ?
-		   jiffies_to_clock_t(expires - jiffies) : 0);
+		   jiffies_delta_to_clock_t(delta) : 0);
 	return 0;
 }
 
-- 
2.55.0.897.gb25b4bd76c-goog


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

* Re: [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src()
  2026-08-28  8:45 ` [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() Eric Dumazet
@ 2026-08-30 15:44   ` Ido Schimmel
  0 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-08-30 15:44 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, David Ahern,
	Simon Horman, netdev, eric.dumazet, Taehee Yoo

On Fri, Aug 28, 2026 at 08:45:27AM +0000, Eric Dumazet wrote:
> When removing a source filter whose count reaches zero, ip6_mc_del1_src()
> unlinks psf from pmc->mca_sources. If the filter was previously active,
> the code moved psf directly into pmc->mca_tomb by updating psf->sf_next.
> 
> Because pmc->mca_sources is traversed locklessly under RCU (e.g. by
> ipv6_chk_mcast_addr()), mutating psf->sf_next before a grace period
> elapses diverts concurrent readers to the tombstone list. Consequently,
> readers miss remaining active sources in pmc->mca_sources and improperly
> examine deleted tombstone entries.
> 
> Fix this by allocating a new tombstone node for pmc->mca_tomb (as done
> in sf_setstate()) and retiring the original psf via kfree_rcu().
> 
> Fixes: 4b200e398953 ("mld: convert ip6_sf_list to RCU")
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Cc: Taehee Yoo <ap420073@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

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

* Re: [PATCH v2 net 2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source()
  2026-08-28  8:45 ` [PATCH v2 net 2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source() Eric Dumazet
@ 2026-08-30 15:44   ` Ido Schimmel
  0 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-08-30 15:44 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, David Ahern,
	Simon Horman, netdev, eric.dumazet, Taehee Yoo

On Fri, Aug 28, 2026 at 08:45:28AM +0000, Eric Dumazet wrote:
> pmc->sflist is read locklessly under rcu_read_lock() by
> inet6_mc_check() during packet reception in the UDP and RAW
> multicast receive paths.
> 
> ip6_mc_source() mutated psl->sl_addr and psl->sl_count in-place
> when adding or removing a source filter. Additionally, when expanding
> the filter buffer, newpsl was published via rcu_assign_pointer()
> before writing the new source into the array.
> 
> Because 16-byte struct in6_addr writes are not atomic and array
> shifting is not synchronized with RCU readers, concurrent readers in
> inet6_mc_check() could read torn IPv6 addresses or observe
> duplicated/missed source entries.
> 
> Fix this by switching ip6_mc_source() to copy-on-write RCU updates:
> allocate and fully populate newpsl before publishing it via
> rcu_assign_pointer(), and reclaim the old filter via kfree_rcu(),
> matching ip6_mc_msfilter().
> 
> Also remove the now unused IP6_SFBLOCK macro.
> 
> Fixes: 882ba1f73c06 ("mld: convert ipv6_mc_socklist->sflist to RCU")
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Cc: Taehee Yoo <ap420073@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

[...]

> @@ -424,58 +422,74 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
>  			goto done;
>  		}
>  
> +		atomic_sub(struct_size(psl, sl_addr, psl->sl_max),
> +			   &sk->sk_omem_alloc);
> +
> +		if (psl->sl_count == 1) {
> +			newpsl = NULL;
> +		} else {
> +			newpsl = sock_kmalloc(sk, struct_size(newpsl, sl_addr,
> +							      psl->sl_count - 1),
> +					      GFP_KERNEL);
> +			if (!newpsl) {
> +				atomic_add(struct_size(psl, sl_addr, psl->sl_max),
> +					   &sk->sk_omem_alloc);
> +				err = -ENOBUFS;
> +				goto done;
> +			}
> +			newpsl->sl_max = psl->sl_count - 1;
> +			newpsl->sl_count = psl->sl_count - 1;
> +			for (j = 0; j < i; j++)
> +				newpsl->sl_addr[j] = psl->sl_addr[j];
> +			for (j = i + 1; j < psl->sl_count; j++)
> +				newpsl->sl_addr[j - 1] = psl->sl_addr[j];
> +		}
> +
>  		/* update the interface filter */
>  		ip6_mc_del_src(idev, group, omode, 1, source, 1);
>  
> -		for (j = i+1; j < psl->sl_count; j++)
> -			psl->sl_addr[j-1] = psl->sl_addr[j];
> -		psl->sl_count--;
> +		rcu_assign_pointer(pmc->sflist, newpsl);
> +		kfree_rcu(psl, rcu);

Earlier we have:

	/* if a source filter was set, must be the same mode as before */
	if (rcu_access_pointer(pmc->sflist)) {
		if (pmc->sfmode != omode) {
			err = -EINVAL;
			goto done;
		}
	} else if (pmc->sfmode != omode) {
		/* allow mode switches for empty-set filters */
		ip6_mc_add_src(idev, group, omode, 0, NULL, 0);
		ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
		pmc->sfmode = omode;
	}

So setting the source list to NULL when deleting the last source in
exclude mode looks like a behavior change, but in a good way.

  IPV6_ADD_MEMBERSHIP(G)          sfmode = MCAST_EXCLUDE
  MCAST_BLOCK_SOURCE(G, S)        sflist = { S }
  MCAST_UNBLOCK_SOURCE(G, S)      was { }, now NULL
  MCAST_JOIN_SOURCE_GROUP(G, S2)  was -EINVAL, now OK

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

* Re: [PATCH v2 net 3/5] ipv6: mcast: fix delay calculation in igmp6_join_group()
  2026-08-28  8:45 ` [PATCH v2 net 3/5] ipv6: mcast: fix delay calculation in igmp6_join_group() Eric Dumazet
@ 2026-08-30 15:44   ` Ido Schimmel
  0 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-08-30 15:44 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, David Ahern,
	Simon Horman, netdev, eric.dumazet, Taehee Yoo

On Fri, Aug 28, 2026 at 08:45:29AM +0000, Eric Dumazet wrote:
> When joining a multicast group, if a report work is already pending
> (e.g. scheduled by a query or a previous join), igmp6_join_group()
> cancels the delayed work and recalculates the delay:
> 
> 	if (cancel_delayed_work(&ma->mca_work)) {
> 		refcount_dec(&ma->mca_refcnt);
> 		delay = ma->mca_work.timer.expires - jiffies;
> 	}
> 
> Unlike igmp6_group_queried(), igmp6_join_group() did not check
> if delay >= interval. This leads to two issues:
> 
> 1. If the timer has already expired (timer.expires <= jiffies), the
>    stale expiry is reused by mod_delayed_work(), causing the second
>    unsolicited report to fire on the very next tick without a
>    randomized delay.
> 2. If the timer was originally armed by a query with a large
>    maximum response delay, delay could exceed
>    unsolicited_report_interval(ma->idev).
> 
> Fix this by initializing delay to unsolicited_report_interval(ma->idev)
> and re-randomizing it with get_random_u32_below(interval) when
> delay >= interval, mirroring the logic in igmp6_group_queried().
> 
> Fixes: 2d9a93b4902b ("mld: convert from timer to delayed work")

Looks like it was broken from the start:

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")

> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Cc: Taehee Yoo <ap420073@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

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

* Re: [PATCH v2 net 4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates
  2026-08-28  8:45 ` [PATCH v2 net 4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates Eric Dumazet
@ 2026-08-30 15:45   ` Ido Schimmel
  0 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-08-30 15:45 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, David Ahern,
	Simon Horman, netdev, eric.dumazet, Taehee Yoo

On Fri, Aug 28, 2026 at 08:45:30AM +0000, Eric Dumazet wrote:
> Several places in net/ipv6/mcast.c update RCU-protected lists
> (np->ipv6_mc_list, idev->mc_list, idev->mc_tomb) using direct pointer
> assignments instead of rcu_assign_pointer():
> 
> 1. In __ipv6_dev_mc_dec(), unlinking a group from idev->mc_list did:
>        *map = ma->next;
>    without rcu_assign_pointer() while concurrent readers traverse
>    idev->mc_list locklessly under rcu_read_lock().
> 2. In ipv6_sock_mc_drop() and __ipv6_sock_mc_close(), unlinking a group
>    from np->ipv6_mc_list directly assigned *lnk = mc_lst->next and
>    np->ipv6_mc_list = mc_lst->next without rcu_assign_pointer(), racing
>    with lockless readers in inet6_mc_check().
> 3. In __ipv6_sock_mc_join(), mc_lst->next was initialized to
>    np->ipv6_mc_list via raw assignment before publishing mc_lst.
> 4. In mld_del_delrec() and __ipv6_dev_mc_inc(), __rcu source pointers
>    passed into rcu_assign_pointer() lacked explicit dereference helpers.
> 
> Fix these by consistently using rcu_assign_pointer() along with
> mc_dereference() / sock_dereference().
> 
> Fixes: 456b61bca8ee ("ipv6: mcast: RCU conversion")
> Fixes: 88e2ca308094 ("mld: convert ifmcaddr6 to RCU")
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> Cc: Taehee Yoo <ap420073@gmail.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

[...]

> @@ -980,7 +985,7 @@ static int __ipv6_dev_mc_inc(struct net_device *dev,
>  		return -ENOMEM;
>  	}
>  
> -	rcu_assign_pointer(mc->next, idev->mc_list);
> +	rcu_assign_pointer(mc->next, mc_dereference(idev->mc_list, idev));
>  	rcu_assign_pointer(idev->mc_list, mc);
>  
>  	mld_del_delrec(idev, mc);

In case you need another version:

diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index aaba4c2aae23..bb1b8957967f 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -763,7 +763,7 @@ static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im)
 			psf->sf_crcount = pmc->mca_crcount;
 	}
 
-	rcu_assign_pointer(pmc->next, idev->mc_tomb);
+	rcu_assign_pointer(pmc->next, mc_dereference(idev->mc_tomb, idev));
 	rcu_assign_pointer(idev->mc_tomb, pmc);
 }


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

* Re: [PATCH v2 net 5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show()
  2026-08-28  8:45 ` [PATCH v2 net 5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show() Eric Dumazet
@ 2026-08-30 15:45   ` Ido Schimmel
  0 siblings, 0 replies; 12+ messages in thread
From: Ido Schimmel @ 2026-08-30 15:45 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, David Ahern,
	Simon Horman, netdev, eric.dumazet

On Fri, Aug 28, 2026 at 08:45:31AM +0000, Eric Dumazet wrote:
> If a multicast group timer has expired but the delayed work has
> not yet run to clear MAF_TIMER_RUNNING, expires - jiffies produces
> a negative value.
> 
> Because unsigned arithmetic was used with jiffies_to_clock_t(),
> expires - jiffies underflows to a huge value and reports invalid
> timer durations in /proc/net/igmp6.
> 
> Use jiffies_delta_to_clock_t() with a signed long delta to properly
> cap expired deltas to 0, matching IPv4 igmp_mc_seq_show() and commit
> a399a8053164 ("time: jiffies_delta_to_clock_t() helper to the rescue").
> 
> Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
> Signed-off-by: Eric Dumazet <edumazet@google.com>

Reviewed-by: Ido Schimmel <idosch@nvidia.com>

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

* Re: [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes
  2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
                   ` (4 preceding siblings ...)
  2026-08-28  8:45 ` [PATCH v2 net 5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show() Eric Dumazet
@ 2026-09-01  1:10 ` patchwork-bot+netdevbpf
  5 siblings, 0 replies; 12+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-01  1:10 UTC (permalink / raw)
  To: Eric Dumazet
  Cc: davem, kuba, pabeni, idosch, dsahern, horms, netdev, eric.dumazet

Hello:

This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:

On Fri, 28 Aug 2026 08:45:26 +0000 you wrote:
> This series addresses several RCU synchronization and timer calculation
> issues identified in IPv6 multicast (MLD) handling within net/ipv6/mcast.c
> while I was working on fixing a syzbot report in net/ipv4/icmp.c.
> 
> Patch 1 fixes an RCU reader diversion in ip6_mc_del1_src() where mutating
> psf->sf_next to insert an unlinked source node into the tombstone list
> diverted concurrent lockless readers (e.g. ipv6_chk_mcast_addr()) into
> pmc->mca_tomb, causing them to miss remaining active sources.
> 
> [...]

Here is the summary with links:
  - [v2,net,1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src()
    https://git.kernel.org/netdev/net/c/93b49239840b
  - [v2,net,2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source()
    https://git.kernel.org/netdev/net/c/c073d1b070f1
  - [v2,net,3/5] ipv6: mcast: fix delay calculation in igmp6_join_group()
    https://git.kernel.org/netdev/net/c/75fa9caeb8aa
  - [v2,net,4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates
    https://git.kernel.org/netdev/net/c/0c8f56c583c3
  - [v2,net,5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show()
    https://git.kernel.org/netdev/net/c/b4cf4a092a7b

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-09-01  1:11 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28  8:45 [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes Eric Dumazet
2026-08-28  8:45 ` [PATCH v2 net 1/5] ipv6: mcast: fix RCU list diversion in ip6_mc_del1_src() Eric Dumazet
2026-08-30 15:44   ` Ido Schimmel
2026-08-28  8:45 ` [PATCH v2 net 2/5] ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source() Eric Dumazet
2026-08-30 15:44   ` Ido Schimmel
2026-08-28  8:45 ` [PATCH v2 net 3/5] ipv6: mcast: fix delay calculation in igmp6_join_group() Eric Dumazet
2026-08-30 15:44   ` Ido Schimmel
2026-08-28  8:45 ` [PATCH v2 net 4/5] ipv6: mcast: use rcu_assign_pointer() for __rcu list updates Eric Dumazet
2026-08-30 15:45   ` Ido Schimmel
2026-08-28  8:45 ` [PATCH v2 net 5/5] ipv6: mcast: use jiffies_delta_to_clock_t() in igmp6_mc_seq_show() Eric Dumazet
2026-08-30 15:45   ` Ido Schimmel
2026-09-01  1:10 ` [PATCH v2 net 0/5] ipv6: mcast: RCU and timer fixes patchwork-bot+netdevbpf

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