diff mbox series

[net-next,v2,5/7] mld: convert ipv6_mc_socklist->sflist to RCU

Message ID 20210213175239.28571-1-ap420073@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series mld: change context from atomic to sleepable | expand

Checks

Context Check Description
netdev/cover_letter success Link
netdev/fixes_present success Link
netdev/patch_count success Link
netdev/tree_selection success Clearly marked for net-next
netdev/subject_prefix success Link
netdev/cc_maintainers success CCed 5 of 5 maintainers
netdev/source_inline success Was 0 now: 0
netdev/verify_signedoff success Link
netdev/module_param success Was 0 now: 0
netdev/build_32bit fail Errors and warnings before: 2949 this patch: 2950
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/verify_fixes success Link
netdev/checkpatch warning WARNING: line length of 81 exceeds 80 columns
netdev/build_allmodconfig_warn fail Errors and warnings before: 3368 this patch: 3369
netdev/header_inline success Link
netdev/stable success Stable not CCed

Commit Message

Taehee Yoo Feb. 13, 2021, 5:52 p.m. UTC
The sflist has been protected by rwlock so that the critical section
is atomic context.
In order to switch this context, changing locking is needed.
The sflist actually already protected by RTNL So if it's converted
to use RCU, its control path context can be switched to sleepable.

Suggested-by: Cong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
---
v1 -> v2:
 - Separated from previous big one patch.

 include/net/if_inet6.h |  4 ++--
 net/ipv6/mcast.c       | 52 ++++++++++++++++++------------------------
 2 files changed, 24 insertions(+), 32 deletions(-)

Comments

kernel test robot Feb. 13, 2021, 7:41 p.m. UTC | #1
Hi Taehee,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on net-next/master]

url:    https://github.com/0day-ci/linux/commits/Taehee-Yoo/mld-change-context-from-atomic-to-sleepable/20210214-015930
base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git 3c5a2fd042d0bfac71a2dfb99515723d318df47b
config: x86_64-randconfig-s022-20210214 (attached as .config)
compiler: gcc-9 (Debian 9.3.0-15) 9.3.0
reproduce:
        # apt-get install sparse
        # sparse version: v0.6.3-215-g0fb77bb6-dirty
        # https://github.com/0day-ci/linux/commit/5a21fa32b1401aa428cd0249ee5b02ddb12cff60
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Taehee-Yoo/mld-change-context-from-atomic-to-sleepable/20210214-015930
        git checkout 5a21fa32b1401aa428cd0249ee5b02ddb12cff60
        # save the attached .config to linux build tree
        make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=x86_64 

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>


"sparse warnings: (new ones prefixed by >>)"
>> net/ipv6/mcast.c:430:17: sparse: sparse: incompatible types in comparison expression (different address spaces):
>> net/ipv6/mcast.c:430:17: sparse:    struct ip6_sf_socklist [noderef] __rcu *
>> net/ipv6/mcast.c:430:17: sparse:    struct ip6_sf_socklist *
   net/ipv6/mcast.c: note: in included file:
   include/net/mld.h:32:43: sparse: sparse: array of flexible structures
   net/ipv6/mcast.c:257:25: sparse: sparse: context imbalance in 'ip6_mc_find_dev_rcu' - different lock contexts for basic block
   net/ipv6/mcast.c:447:9: sparse: sparse: context imbalance in 'ip6_mc_source' - unexpected unlock
   net/ipv6/mcast.c:536:9: sparse: sparse: context imbalance in 'ip6_mc_msfilter' - unexpected unlock
   net/ipv6/mcast.c:583:21: sparse: sparse: context imbalance in 'ip6_mc_msfget' - unexpected unlock
   net/ipv6/mcast.c:2724:25: sparse: sparse: context imbalance in 'igmp6_mc_get_next' - unexpected unlock
   net/ipv6/mcast.c:2746:9: sparse: sparse: context imbalance in 'igmp6_mc_get_idx' - wrong count at exit
   net/ipv6/mcast.c:2773:9: sparse: sparse: context imbalance in 'igmp6_mc_seq_stop' - unexpected unlock
   net/ipv6/mcast.c:2845:31: sparse: sparse: context imbalance in 'igmp6_mcf_get_next' - unexpected unlock
   net/ipv6/mcast.c:2877:9: sparse: sparse: context imbalance in 'igmp6_mcf_get_idx' - wrong count at exit
   net/ipv6/mcast.c:2894:9: sparse: sparse: context imbalance in 'igmp6_mcf_seq_next' - wrong count at exit
   net/ipv6/mcast.c:2907:17: sparse: sparse: context imbalance in 'igmp6_mcf_seq_stop' - unexpected unlock

vim +430 net/ipv6/mcast.c

   325	
   326	int ip6_mc_source(int add, int omode, struct sock *sk,
   327		struct group_source_req *pgsr)
   328	{
   329		struct in6_addr *source, *group;
   330		struct ipv6_mc_socklist *pmc;
   331		struct inet6_dev *idev;
   332		struct ipv6_pinfo *inet6 = inet6_sk(sk);
   333		struct ip6_sf_socklist *psl;
   334		struct net *net = sock_net(sk);
   335		int i, j, rv;
   336		int leavegroup = 0;
   337		int err;
   338	
   339		source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr;
   340		group = &((struct sockaddr_in6 *)&pgsr->gsr_group)->sin6_addr;
   341	
   342		if (!ipv6_addr_is_multicast(group))
   343			return -EINVAL;
   344	
   345		rcu_read_lock();
   346		idev = ip6_mc_find_dev_rcu(net, group, pgsr->gsr_interface);
   347		if (!idev) {
   348			rcu_read_unlock();
   349			return -ENODEV;
   350		}
   351	
   352		err = -EADDRNOTAVAIL;
   353	
   354		for_each_pmc_rcu(inet6, pmc) {
   355			if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface)
   356				continue;
   357			if (ipv6_addr_equal(&pmc->addr, group))
   358				break;
   359		}
   360		if (!pmc) {		/* must have a prior join */
   361			err = -EINVAL;
   362			goto done;
   363		}
   364		/* if a source filter was set, must be the same mode as before */
   365		if (rcu_access_pointer(pmc->sflist)) {
   366			if (pmc->sfmode != omode) {
   367				err = -EINVAL;
   368				goto done;
   369			}
   370		} else if (pmc->sfmode != omode) {
   371			/* allow mode switches for empty-set filters */
   372			ip6_mc_add_src(idev, group, omode, 0, NULL, 0);
   373			ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
   374			pmc->sfmode = omode;
   375		}
   376	
   377		psl = rtnl_dereference(pmc->sflist);
   378		if (!add) {
   379			if (!psl)
   380				goto done;	/* err = -EADDRNOTAVAIL */
   381			rv = !0;
   382			for (i = 0; i < psl->sl_count; i++) {
   383				rv = !ipv6_addr_equal(&psl->sl_addr[i], source);
   384				if (rv == 0)
   385					break;
   386			}
   387			if (rv)		/* source not found */
   388				goto done;	/* err = -EADDRNOTAVAIL */
   389	
   390			/* special case - (INCLUDE, empty) == LEAVE_GROUP */
   391			if (psl->sl_count == 1 && omode == MCAST_INCLUDE) {
   392				leavegroup = 1;
   393				goto done;
   394			}
   395	
   396			/* update the interface filter */
   397			ip6_mc_del_src(idev, group, omode, 1, source, 1);
   398	
   399			for (j = i+1; j < psl->sl_count; j++)
   400				psl->sl_addr[j-1] = psl->sl_addr[j];
   401			psl->sl_count--;
   402			err = 0;
   403			goto done;
   404		}
   405		/* else, add a new source to the filter */
   406	
   407		if (psl && psl->sl_count >= sysctl_mld_max_msf) {
   408			err = -ENOBUFS;
   409			goto done;
   410		}
   411		if (!psl || psl->sl_count == psl->sl_max) {
   412			struct ip6_sf_socklist *newpsl;
   413			int count = IP6_SFBLOCK;
   414	
   415			if (psl)
   416				count += psl->sl_max;
   417			newpsl = sock_kmalloc(sk, IP6_SFLSIZE(count), GFP_ATOMIC);
   418			if (!newpsl) {
   419				err = -ENOBUFS;
   420				goto done;
   421			}
   422			newpsl->sl_max = count;
   423			newpsl->sl_count = count - IP6_SFBLOCK;
   424			if (psl) {
   425				for (i = 0; i < psl->sl_count; i++)
   426					newpsl->sl_addr[i] = psl->sl_addr[i];
   427				atomic_sub(IP6_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
   428				kfree_rcu(psl, rcu);
   429			}
 > 430			rcu_assign_pointer(psl, newpsl);
   431			rcu_assign_pointer(pmc->sflist, psl);
   432		}
   433		rv = 1;	/* > 0 for insert logic below if sl_count is 0 */
   434		for (i = 0; i < psl->sl_count; i++) {
   435			rv = !ipv6_addr_equal(&psl->sl_addr[i], source);
   436			if (rv == 0) /* There is an error in the address. */
   437				goto done;
   438		}
   439		for (j = psl->sl_count-1; j >= i; j--)
   440			psl->sl_addr[j+1] = psl->sl_addr[j];
   441		psl->sl_addr[i] = *source;
   442		psl->sl_count++;
   443		err = 0;
   444		/* update the interface list */
   445		ip6_mc_add_src(idev, group, omode, 1, source, 1);
   446	done:
   447		read_unlock_bh(&idev->lock);
   448		rcu_read_unlock();
   449		if (leavegroup)
   450			err = ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group);
   451		return err;
   452	}
   453	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
Taehee Yoo Feb. 14, 2021, 11:32 a.m. UTC | #2
On 21. 2. 14. 오전 4:41, kernel test robot wrote:
> Hi Taehee,
> 
> Thank you for the patch! Perhaps something to improve:
> 
> [auto build test WARNING on net-next/master]
> 
> url:    https://github.com/0day-ci/linux/commits/Taehee-Yoo/mld-change-context-from-atomic-to-sleepable/20210214-015930
> base:   https://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next.git 3c5a2fd042d0bfac71a2dfb99515723d318df47b
> config: x86_64-randconfig-s022-20210214 (attached as .config)
> compiler: gcc-9 (Debian 9.3.0-15) 9.3.0
> reproduce:
>          # apt-get install sparse
>          # sparse version: v0.6.3-215-g0fb77bb6-dirty
>          # https://github.com/0day-ci/linux/commit/5a21fa32b1401aa428cd0249ee5b02ddb12cff60
>          git remote add linux-review https://github.com/0day-ci/linux
>          git fetch --no-tags linux-review Taehee-Yoo/mld-change-context-from-atomic-to-sleepable/20210214-015930
>          git checkout 5a21fa32b1401aa428cd0249ee5b02ddb12cff60
>          # save the attached .config to linux build tree
>          make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' ARCH=x86_64
> 
> If you fix the issue, kindly add following tag as appropriate
> Reported-by: kernel test robot <lkp@intel.com>
> 
> 
> "sparse warnings: (new ones prefixed by >>)"
>>> net/ipv6/mcast.c:430:17: sparse: sparse: incompatible types in comparison expression (different address spaces):
>>> net/ipv6/mcast.c:430:17: sparse:    struct ip6_sf_socklist [noderef] __rcu *
>>> net/ipv6/mcast.c:430:17: sparse:    struct ip6_sf_socklist *
>     net/ipv6/mcast.c: note: in included file:
>     include/net/mld.h:32:43: sparse: sparse: array of flexible structures
>     net/ipv6/mcast.c:257:25: sparse: sparse: context imbalance in 'ip6_mc_find_dev_rcu' - different lock contexts for basic block
>     net/ipv6/mcast.c:447:9: sparse: sparse: context imbalance in 'ip6_mc_source' - unexpected unlock
>     net/ipv6/mcast.c:536:9: sparse: sparse: context imbalance in 'ip6_mc_msfilter' - unexpected unlock
>     net/ipv6/mcast.c:583:21: sparse: sparse: context imbalance in 'ip6_mc_msfget' - unexpected unlock
>     net/ipv6/mcast.c:2724:25: sparse: sparse: context imbalance in 'igmp6_mc_get_next' - unexpected unlock
>     net/ipv6/mcast.c:2746:9: sparse: sparse: context imbalance in 'igmp6_mc_get_idx' - wrong count at exit
>     net/ipv6/mcast.c:2773:9: sparse: sparse: context imbalance in 'igmp6_mc_seq_stop' - unexpected unlock
>     net/ipv6/mcast.c:2845:31: sparse: sparse: context imbalance in 'igmp6_mcf_get_next' - unexpected unlock
>     net/ipv6/mcast.c:2877:9: sparse: sparse: context imbalance in 'igmp6_mcf_get_idx' - wrong count at exit
>     net/ipv6/mcast.c:2894:9: sparse: sparse: context imbalance in 'igmp6_mcf_seq_next' - wrong count at exit
>     net/ipv6/mcast.c:2907:17: sparse: sparse: context imbalance in 'igmp6_mcf_seq_stop' - unexpected unlock
> 
> vim +430 net/ipv6/mcast.c
> 
>     325	
>     326	int ip6_mc_source(int add, int omode, struct sock *sk,
>     327		struct group_source_req *pgsr)
>     328	{
>     329		struct in6_addr *source, *group;
>     330		struct ipv6_mc_socklist *pmc;
>     331		struct inet6_dev *idev;
>     332		struct ipv6_pinfo *inet6 = inet6_sk(sk);
>     333		struct ip6_sf_socklist *psl;
>     334		struct net *net = sock_net(sk);
>     335		int i, j, rv;
>     336		int leavegroup = 0;
>     337		int err;
>     338	
>     339		source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr;
>     340		group = &((struct sockaddr_in6 *)&pgsr->gsr_group)->sin6_addr;
>     341	
>     342		if (!ipv6_addr_is_multicast(group))
>     343			return -EINVAL;
>     344	
>     345		rcu_read_lock();
>     346		idev = ip6_mc_find_dev_rcu(net, group, pgsr->gsr_interface);
>     347		if (!idev) {
>     348			rcu_read_unlock();
>     349			return -ENODEV;
>     350		}
>     351	
>     352		err = -EADDRNOTAVAIL;
>     353	
>     354		for_each_pmc_rcu(inet6, pmc) {
>     355			if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface)
>     356				continue;
>     357			if (ipv6_addr_equal(&pmc->addr, group))
>     358				break;
>     359		}
>     360		if (!pmc) {		/* must have a prior join */
>     361			err = -EINVAL;
>     362			goto done;
>     363		}
>     364		/* if a source filter was set, must be the same mode as before */
>     365		if (rcu_access_pointer(pmc->sflist)) {
>     366			if (pmc->sfmode != omode) {
>     367				err = -EINVAL;
>     368				goto done;
>     369			}
>     370		} else if (pmc->sfmode != omode) {
>     371			/* allow mode switches for empty-set filters */
>     372			ip6_mc_add_src(idev, group, omode, 0, NULL, 0);
>     373			ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
>     374			pmc->sfmode = omode;
>     375		}
>     376	
>     377		psl = rtnl_dereference(pmc->sflist);
>     378		if (!add) {
>     379			if (!psl)
>     380				goto done;	/* err = -EADDRNOTAVAIL */
>     381			rv = !0;
>     382			for (i = 0; i < psl->sl_count; i++) {
>     383				rv = !ipv6_addr_equal(&psl->sl_addr[i], source);
>     384				if (rv == 0)
>     385					break;
>     386			}
>     387			if (rv)		/* source not found */
>     388				goto done;	/* err = -EADDRNOTAVAIL */
>     389	
>     390			/* special case - (INCLUDE, empty) == LEAVE_GROUP */
>     391			if (psl->sl_count == 1 && omode == MCAST_INCLUDE) {
>     392				leavegroup = 1;
>     393				goto done;
>     394			}
>     395	
>     396			/* update the interface filter */
>     397			ip6_mc_del_src(idev, group, omode, 1, source, 1);
>     398	
>     399			for (j = i+1; j < psl->sl_count; j++)
>     400				psl->sl_addr[j-1] = psl->sl_addr[j];
>     401			psl->sl_count--;
>     402			err = 0;
>     403			goto done;
>     404		}
>     405		/* else, add a new source to the filter */
>     406	
>     407		if (psl && psl->sl_count >= sysctl_mld_max_msf) {
>     408			err = -ENOBUFS;
>     409			goto done;
>     410		}
>     411		if (!psl || psl->sl_count == psl->sl_max) {
>     412			struct ip6_sf_socklist *newpsl;
>     413			int count = IP6_SFBLOCK;
>     414	
>     415			if (psl)
>     416				count += psl->sl_max;
>     417			newpsl = sock_kmalloc(sk, IP6_SFLSIZE(count), GFP_ATOMIC);
>     418			if (!newpsl) {
>     419				err = -ENOBUFS;
>     420				goto done;
>     421			}
>     422			newpsl->sl_max = count;
>     423			newpsl->sl_count = count - IP6_SFBLOCK;
>     424			if (psl) {
>     425				for (i = 0; i < psl->sl_count; i++)
>     426					newpsl->sl_addr[i] = psl->sl_addr[i];
>     427				atomic_sub(IP6_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
>     428				kfree_rcu(psl, rcu);
>     429			}
>   > 430			rcu_assign_pointer(psl, newpsl);
>     431			rcu_assign_pointer(pmc->sflist, psl);
>     432		}
>     433		rv = 1;	/* > 0 for insert logic below if sl_count is 0 */
>     434		for (i = 0; i < psl->sl_count; i++) {
>     435			rv = !ipv6_addr_equal(&psl->sl_addr[i], source);
>     436			if (rv == 0) /* There is an error in the address. */
>     437				goto done;
>     438		}
>     439		for (j = psl->sl_count-1; j >= i; j--)
>     440			psl->sl_addr[j+1] = psl->sl_addr[j];
>     441		psl->sl_addr[i] = *source;
>     442		psl->sl_count++;
>     443		err = 0;
>     444		/* update the interface list */
>     445		ip6_mc_add_src(idev, group, omode, 1, source, 1);
>     446	done:
>     447		read_unlock_bh(&idev->lock);
>     448		rcu_read_unlock();
>     449		if (leavegroup)
>     450			err = ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group);
>     451		return err;
>     452	}
>     453	
> 
> ---
> 0-DAY CI Kernel Test Service, Intel Corporation
> https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
> 

I will add __rcu annotation in a v3 patch to avoid sparse warning.
Thanks!
diff mbox series

Patch

diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h
index 4d9855be644c..d8507bef0a0c 100644
--- a/include/net/if_inet6.h
+++ b/include/net/if_inet6.h
@@ -78,6 +78,7 @@  struct inet6_ifaddr {
 struct ip6_sf_socklist {
 	unsigned int		sl_max;
 	unsigned int		sl_count;
+	struct rcu_head		rcu;
 	struct in6_addr		sl_addr[];
 };
 
@@ -91,8 +92,7 @@  struct ipv6_mc_socklist {
 	int			ifindex;
 	unsigned int		sfmode;		/* MCAST_{INCLUDE,EXCLUDE} */
 	struct ipv6_mc_socklist __rcu *next;
-	rwlock_t		sflock;
-	struct ip6_sf_socklist	*sflist;
+	struct ip6_sf_socklist	__rcu *sflist;
 	struct rcu_head		rcu;
 };
 
diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
index e80b78b1a8a7..cffa2eeb88c5 100644
--- a/net/ipv6/mcast.c
+++ b/net/ipv6/mcast.c
@@ -178,8 +178,7 @@  static int __ipv6_sock_mc_join(struct sock *sk, int ifindex,
 
 	mc_lst->ifindex = dev->ifindex;
 	mc_lst->sfmode = mode;
-	rwlock_init(&mc_lst->sflock);
-	mc_lst->sflist = NULL;
+	RCU_INIT_POINTER(mc_lst->sflist, NULL);
 
 	/*
 	 *	now add/increase the group membership on the device
@@ -335,7 +334,6 @@  int ip6_mc_source(int add, int omode, struct sock *sk,
 	struct net *net = sock_net(sk);
 	int i, j, rv;
 	int leavegroup = 0;
-	int pmclocked = 0;
 	int err;
 
 	source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr;
@@ -364,7 +362,7 @@  int ip6_mc_source(int add, int omode, struct sock *sk,
 		goto done;
 	}
 	/* if a source filter was set, must be the same mode as before */
-	if (pmc->sflist) {
+	if (rcu_access_pointer(pmc->sflist)) {
 		if (pmc->sfmode != omode) {
 			err = -EINVAL;
 			goto done;
@@ -376,10 +374,7 @@  int ip6_mc_source(int add, int omode, struct sock *sk,
 		pmc->sfmode = omode;
 	}
 
-	write_lock(&pmc->sflock);
-	pmclocked = 1;
-
-	psl = pmc->sflist;
+	psl = rtnl_dereference(pmc->sflist);
 	if (!add) {
 		if (!psl)
 			goto done;	/* err = -EADDRNOTAVAIL */
@@ -429,9 +424,11 @@  int ip6_mc_source(int add, int omode, struct sock *sk,
 		if (psl) {
 			for (i = 0; i < psl->sl_count; i++)
 				newpsl->sl_addr[i] = psl->sl_addr[i];
-			sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max));
+			atomic_sub(IP6_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
+			kfree_rcu(psl, rcu);
 		}
-		pmc->sflist = psl = newpsl;
+		rcu_assign_pointer(psl, newpsl);
+		rcu_assign_pointer(pmc->sflist, psl);
 	}
 	rv = 1;	/* > 0 for insert logic below if sl_count is 0 */
 	for (i = 0; i < psl->sl_count; i++) {
@@ -447,8 +444,6 @@  int ip6_mc_source(int add, int omode, struct sock *sk,
 	/* update the interface list */
 	ip6_mc_add_src(idev, group, omode, 1, source, 1);
 done:
-	if (pmclocked)
-		write_unlock(&pmc->sflock);
 	read_unlock_bh(&idev->lock);
 	rcu_read_unlock();
 	if (leavegroup)
@@ -526,17 +521,16 @@  int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf,
 		(void) ip6_mc_add_src(idev, group, gsf->gf_fmode, 0, NULL, 0);
 	}
 
-	write_lock(&pmc->sflock);
-	psl = pmc->sflist;
+	psl = rtnl_dereference(pmc->sflist);
 	if (psl) {
 		(void) ip6_mc_del_src(idev, group, pmc->sfmode,
 			psl->sl_count, psl->sl_addr, 0);
-		sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max));
+		atomic_sub(IP6_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
+		kfree_rcu(psl, rcu);
 	} else
 		(void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
-	pmc->sflist = newpsl;
+	rcu_assign_pointer(pmc->sflist, newpsl);
 	pmc->sfmode = gsf->gf_fmode;
-	write_unlock(&pmc->sflock);
 	err = 0;
 done:
 	read_unlock_bh(&idev->lock);
@@ -585,16 +579,14 @@  int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
 	if (!pmc)		/* must have a prior join */
 		goto done;
 	gsf->gf_fmode = pmc->sfmode;
-	psl = pmc->sflist;
+	psl = rtnl_dereference(pmc->sflist);
 	count = psl ? psl->sl_count : 0;
 	read_unlock_bh(&idev->lock);
 	rcu_read_unlock();
 
 	copycount = count < gsf->gf_numsrc ? count : gsf->gf_numsrc;
 	gsf->gf_numsrc = count;
-	/* changes to psl require the socket lock, and a write lock
-	 * on pmc->sflock. We have the socket lock so reading here is safe.
-	 */
+
 	for (i = 0; i < copycount; i++, p++) {
 		struct sockaddr_in6 *psin6;
 		struct sockaddr_storage ss;
@@ -630,8 +622,7 @@  bool inet6_mc_check(struct sock *sk, const struct in6_addr *mc_addr,
 		rcu_read_unlock();
 		return np->mc_all;
 	}
-	read_lock(&mc->sflock);
-	psl = mc->sflist;
+	psl = rcu_dereference(mc->sflist);
 	if (!psl) {
 		rv = mc->sfmode == MCAST_EXCLUDE;
 	} else {
@@ -646,7 +637,6 @@  bool inet6_mc_check(struct sock *sk, const struct in6_addr *mc_addr,
 		if (mc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
 			rv = false;
 	}
-	read_unlock(&mc->sflock);
 	rcu_read_unlock();
 
 	return rv;
@@ -2448,19 +2438,21 @@  static void igmp6_join_group(struct ifmcaddr6 *ma)
 static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
 			    struct inet6_dev *idev)
 {
+	struct ip6_sf_socklist *psl;
 	int err;
 
-	write_lock_bh(&iml->sflock);
-	if (!iml->sflist) {
+	psl = rtnl_dereference(iml->sflist);
+
+	if (!psl) {
 		/* any-source empty exclude case */
 		err = ip6_mc_del_src(idev, &iml->addr, iml->sfmode, 0, NULL, 0);
 	} else {
 		err = ip6_mc_del_src(idev, &iml->addr, iml->sfmode,
-				iml->sflist->sl_count, iml->sflist->sl_addr, 0);
-		sock_kfree_s(sk, iml->sflist, IP6_SFLSIZE(iml->sflist->sl_max));
-		iml->sflist = NULL;
+				psl->sl_count, psl->sl_addr, 0);
+		RCU_INIT_POINTER(iml->sflist, NULL);
+		atomic_sub(IP6_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
+		kfree_rcu(psl, rcu);
 	}
-	write_unlock_bh(&iml->sflock);
 	return err;
 }