mirror of
https://github.com/torvalds/linux.git
synced 2026-09-13 15:40:03 +02:00
ipv6: mcast: use copy-on-write RCU updates in ip6_mc_source()
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: 882ba1f73c ("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>
Link: https://patch.msgid.link/20260828084531.1826790-3-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
93b4923984
commit
c073d1b070
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user