* [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;
as well as URLs for NNTP newsgroup(s).