mirror of
https://github.com/torvalds/linux.git
synced 2026-09-11 20:13:02 +02:00
Merge branch 'ipv6-mcast-rcu-and-timer-fixes'
Eric Dumazet says: ==================== ipv6: mcast: RCU and timer fixes 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. ==================== Link: https://patch.msgid.link/20260828084531.1826790-1-edumazet@google.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
ec12bdd568
|
|
@ -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;
|
||||
|
|
|
|||
148
net/ipv6/mcast.c
148
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -355,12 +358,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 +412,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 +425,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) {
|
||||
for (i = 0; i < psl->sl_count; i++) {
|
||||
if (ipv6_addr_equal(&psl->sl_addr[i], source))
|
||||
goto done; /* err = -EADDRNOTAVAIL */
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
rcu_assign_pointer(pmc->sflist, newpsl);
|
||||
kfree_rcu(psl, rcu);
|
||||
psl = newpsl;
|
||||
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;
|
||||
}
|
||||
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;
|
||||
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];
|
||||
}
|
||||
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_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);
|
||||
|
|
@ -784,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) {
|
||||
|
|
@ -966,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);
|
||||
|
|
@ -1000,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,
|
||||
|
|
@ -2351,14 +2371,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;
|
||||
}
|
||||
|
|
@ -2621,7 +2645,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);
|
||||
|
||||
|
|
@ -2630,13 +2654,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 |
|
||||
|
|
@ -3001,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",
|
||||
|
|
@ -3009,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user