net: fix mcast RCU splats
Subsystems (3)
Paolo Abeni
maintainercover net: fix mcast RCU splats
This series addresses the RCU splat triggered by the forwarding mroute tests.
The first patch does not address any specific issue, but makes the following ones more clear. Patch 2 and 3 address the issue for ipv6 and ipv4 respectively.
Paolo Abeni (3):
ipmr: add debug check for mr table cleanup
ip6mr: fix tables suspicious RCU usage
ipmr: fix tables suspicious RCU usage
net/ipv4/ipmr.c | 51 ++++++++++++++++++++++++++++++++++++------------ net/ipv6/ip6mr.c | 47 +++++++++++++++++++++++++++++++++----------- 2 files changed, 74 insertions(+), 24 deletions(-)
Signature · 1 line
2.45.2
Paolo Abeni
maintainerPATCH 1/3 ipmr: add debug check for mr table cleanup
The multicast route tables lifecycle, for both ipv4 and ipv6, is protected by RCU using the RTNL lock for write access. In many places a table pointer escapes the RCU (or RTNL) protected critical section, but such scenarios are actually safe because tables are deleted only at namespace cleanup time or just after allocation, in case of default rule creation failure.
Tables freed at namespace cleanup time are assured to be alive for the whole netns lifetime; tables freed just after creation time are never exposed to other possible users.
Ensure that the free conditions are respected in ip{,6}mr_free_table, to document the locking schema and to prevent future possible introduction of 'table del' operation from breaking it.
--- net/ipv4/ipmr.c | 9 +++++++++ net/ipv6/ip6mr.c | 9 +++++++++ 2 files changed, 18 insertions(+)
net/ipv4/ipmr.c +9 -0
@@ -358,6 +358,11 @@ bool ipmr_rule_default(const struct fib_rule *rule)EXPORT_SYMBOL (ipmr_rule_default);#endif +static bool ipmr_can_free_table (struct net *net) +{ +return !check_net (net) || !net->ipv4.mr_rules_ops; +} +static inline int ipmr_hash_cmp (struct rhashtable_compare_arg *arg,const void *ptr) {
@@ -413,6 +418,10 @@ static struct mr_table *ipmr_new_table(struct net *net, u32 id)static void ipmr_free_table (struct mr_table *mrt) { +struct net *net =read_pnet (&mrt->net); + +DEBUG_NET_WARN_ON_ONCE (!ipmr_can_free_table (net)); +timer_shutdown_sync (&mrt->ipmr_expire_timer);mroute_clean_tables (mrt,MRT_FLUSH_VIFS |MRT_FLUSH_VIFS_STATIC |MRT_FLUSH_MFC |MRT_FLUSH_MFC_STATIC );
net/ipv6/ip6mr.c +9 -0
@@ -341,6 +341,11 @@ static unsigned int ip6mr_rules_seq_read(const struct net *net) }#endif +static bool ip6mr_can_free_table (struct net *net) +{ +return !check_net (net) || !net->ipv6.mr6_rules_ops; +} +static int ip6mr_hash_cmp (struct rhashtable_compare_arg *arg,const void *ptr) {
@@ -392,6 +397,10 @@ static struct mr_table *ip6mr_new_table(struct net *net, u32 id)static void ip6mr_free_table (struct mr_table *mrt) { +struct net *net =read_pnet (&mrt->net); + +DEBUG_NET_WARN_ON_ONCE (!ip6mr_can_free_table (net)); +timer_shutdown_sync (&mrt->ipmr_expire_timer);mroute_clean_tables (mrt,MRT6_FLUSH_MIFS |MRT6_FLUSH_MIFS_STATIC |MRT6_FLUSH_MFC |MRT6_FLUSH_MFC_STATIC );
Signature · 1 line
2.45.2
Paolo Abeni
maintainerPATCH 2/3 ip6mr: fix tables suspicious RCU usage
In reply to Paolo AbeniSeveral places call ip6mr_get_table() with no RCU nor RTNL lock. Add RCU protection inside such helper and provide a lockless variant for the few callers that already acquired the relevant lock.
Note that some users additionally reference the table outside the RCU lock. That is actually safe as the table deletion can happen only after all table accesses are completed.
--- net/ipv6/ip6mr.c | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-)
net/ipv6/ip6mr.c +27 -11
@@ -125,7 +125,7 @@ static struct mr_table *ip6mr_mr_table_iter(struct net *net,return ret; } -static struct mr_table *ip6mr_get_table (struct net *net,u32 id) +static struct mr_table *__ip6mr_get_table (struct net *net,u32 id) {struct mr_table *mrt;
@@ -136,6 +136,16 @@ static struct mr_table *ip6mr_get_table(struct net *net, u32 id)return NULL ; } +static struct mr_table *ip6mr_get_table (struct net *net,u32 id) +{ +struct mr_table *mrt; + +rcu_read_lock (); + mrt =__ip6mr_get_table (net, id); +rcu_read_unlock (); +return mrt; +} +static int ip6mr_fib_lookup (struct net *net,struct flowi6 *flp6,struct mr_table **mrt) {
@@ -177,7 +187,7 @@ static int ip6mr_rule_action(struct fib_rule *rule, struct flowi *flp, arg->table =fib_rule_get_table (rule, arg); - mrt =ip6mr_get_table (rule->fr_net, arg->table); + mrt =__ip6mr_get_table (rule->fr_net, arg->table);if (!mrt)return -EAGAIN ; res->mrt = mrt;
@@ -304,6 +314,8 @@ static struct mr_table *ip6mr_get_table(struct net *net, u32 id)return net->ipv6.mrt6; } +#define __ip6mr_get_table ip6mr_get_table +static int ip6mr_fib_lookup (struct net *net,struct flowi6 *flp6,struct mr_table **mrt) {
@@ -387,7 +399,7 @@ static struct mr_table *ip6mr_new_table(struct net *net, u32 id) {struct mr_table *mrt; - mrt =ip6mr_get_table (net, id); + mrt =__ip6mr_get_table (net, id);if (mrt)return mrt;
@@ -420,13 +432,15 @@ static void *ip6mr_vif_seq_start(struct seq_file *seq, loff_t *pos)struct net *net =seq_file_net (seq);struct mr_table *mrt; - mrt =ip6mr_get_table (net,RT6_TABLE_DFLT ); -if (!mrt) +rcu_read_lock (); + mrt =__ip6mr_get_table (net,RT6_TABLE_DFLT ); +if (!mrt) { +rcu_read_unlock ();return ERR_PTR (-ENOENT ); + } iter->mrt = mrt; -rcu_read_lock ();return mr_vif_seq_start (seq, pos); }
@@ -2287,11 +2301,13 @@ int ip6mr_get_route(struct net *net, struct sk_buff *skb, struct rtmsg *rtm,struct mfc6_cache *cache;struct rt6_info *rt =dst_rt6_info (skb_dst (skb)); - mrt =ip6mr_get_table (net,RT6_TABLE_DFLT ); -if (!mrt) +rcu_read_lock (); + mrt =__ip6mr_get_table (net,RT6_TABLE_DFLT ); +if (!mrt) { +rcu_read_unlock ();return -ENOENT ; + } -rcu_read_lock (); cache =ip6mr_cache_find (mrt, &rt->rt6i_src.addr, &rt->rt6i_dst.addr);if (!cache && skb->dev) {int vif =ip6mr_find_vif (mrt, skb->dev);
@@ -2571,7 +2587,7 @@ static int ip6mr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, grp =nla_get_in6_addr (tb[RTA_DST ]); tableid =nla_get_u32_default (tb[RTA_TABLE ],0 ); - mrt =ip6mr_get_table (net, tableid ?:RT_TABLE_DEFAULT ); + mrt =__ip6mr_get_table (net, tableid ?:RT_TABLE_DEFAULT );if (!mrt) {NL_SET_ERR_MSG_MOD (extack,"MR table does not exist" );return -ENOENT ;
@@ -2618,7 +2634,7 @@ static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)if (filter.table_id) {struct mr_table *mrt; - mrt =ip6mr_get_table (sock_net (skb->sk), filter.table_id); + mrt =__ip6mr_get_table (sock_net (skb->sk), filter.table_id);if (!mrt) {if (rtnl_msg_family (cb->nlh) !=RTNL_FAMILY_IP6MR )return skb->len;
Signature · 1 line
2.45.2
Paolo Abeni
maintainerPATCH 3/3 ipmr: fix tables suspicious RCU usage
In reply to Paolo AbeniSimilar to the previous patch, plumb the RCU lock inside the ipmr_get_table(), provided a lockless variant and apply the latter in the few spots were the lock is already held.
--- net/ipv4/ipmr.c | 42 +++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-)
net/ipv4/ipmr.c +29 -13
@@ -137,7 +137,7 @@ static struct mr_table *ipmr_mr_table_iter(struct net *net,return ret; } -static struct mr_table *ipmr_get_table (struct net *net,u32 id) +static struct mr_table *__ipmr_get_table (struct net *net,u32 id) {struct mr_table *mrt;
@@ -148,6 +148,16 @@ static struct mr_table *ipmr_get_table(struct net *net, u32 id)return NULL ; } +static struct mr_table *ipmr_get_table (struct net *net,u32 id) +{ +struct mr_table *mrt; + +rcu_read_lock (); + mrt =__ipmr_get_table (net, id); +rcu_read_unlock (); +return mrt; +} +static int ipmr_fib_lookup (struct net *net,struct flowi4 *flp4,struct mr_table **mrt) {
@@ -189,7 +199,7 @@ static int ipmr_rule_action(struct fib_rule *rule, struct flowi *flp, arg->table =fib_rule_get_table (rule, arg); - mrt =ipmr_get_table (rule->fr_net, arg->table); + mrt =__ipmr_get_table (rule->fr_net, arg->table);if (!mrt)return -EAGAIN ; res->mrt = mrt;
@@ -315,6 +325,8 @@ static struct mr_table *ipmr_get_table(struct net *net, u32 id)return net->ipv4.mrt; } +#define __ipmr_get_table ipmr_get_table +static int ipmr_fib_lookup (struct net *net,struct flowi4 *flp4,struct mr_table **mrt) {
@@ -408,7 +420,7 @@ static struct mr_table *ipmr_new_table(struct net *net, u32 id)if (id !=RT_TABLE_DEFAULT && id >=1000000000 )return ERR_PTR (-EINVAL ); - mrt =ipmr_get_table (net, id); + mrt =__ipmr_get_table (net, id);if (mrt)return mrt;
@@ -1383,7 +1395,7 @@ int ip_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval, goto out_unlock; } - mrt =ipmr_get_table (net,raw_sk (sk)->ipmr_table ? :RT_TABLE_DEFAULT ); + mrt =__ipmr_get_table (net,raw_sk (sk)->ipmr_table ? :RT_TABLE_DEFAULT );if (!mrt) { ret = -ENOENT ; goto out_unlock;
@@ -2271,11 +2283,13 @@ int ipmr_get_route(struct net *net, struct sk_buff *skb,struct mr_table *mrt;int err; - mrt =ipmr_get_table (net,RT_TABLE_DEFAULT ); -if (!mrt) +rcu_read_lock (); + mrt =__ipmr_get_table (net,RT_TABLE_DEFAULT ); +if (!mrt) { +rcu_read_unlock ();return -ENOENT ; + } -rcu_read_lock (); cache =ipmr_cache_find (mrt, saddr, daddr);if (!cache && skb->dev) {int vif =ipmr_find_vif (mrt, skb->dev);
@@ -2559,7 +2573,7 @@ static int ipmr_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, grp =nla_get_in_addr_default (tb[RTA_DST ],0 ); tableid =nla_get_u32_default (tb[RTA_TABLE ],0 ); - mrt =ipmr_get_table (net, tableid ? tableid :RT_TABLE_DEFAULT ); + mrt =__ipmr_get_table (net, tableid ? tableid :RT_TABLE_DEFAULT );if (!mrt) { err = -ENOENT ; goto errout_free;
@@ -2613,7 +2627,7 @@ static int ipmr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)if (filter.table_id) {struct mr_table *mrt; - mrt =ipmr_get_table (sock_net (skb->sk), filter.table_id); + mrt =__ipmr_get_table (sock_net (skb->sk), filter.table_id);if (!mrt) {if (rtnl_msg_family (cb->nlh) !=RTNL_FAMILY_IPMR )return skb->len;
@@ -2721,7 +2735,7 @@ static int rtm_to_ipmr_mfcc(struct net *net, struct nlmsghdr *nlh,break ; } } - mrt =ipmr_get_table (net, tblid); + mrt =__ipmr_get_table (net, tblid);if (!mrt) { ret = -ENOENT ; goto out;
@@ -2929,13 +2943,15 @@ static void *ipmr_vif_seq_start(struct seq_file *seq, loff_t *pos)struct net *net =seq_file_net (seq);struct mr_table *mrt; - mrt =ipmr_get_table (net,RT_TABLE_DEFAULT ); -if (!mrt) +rcu_read_lock (); + mrt =__ipmr_get_table (net,RT_TABLE_DEFAULT ); +if (!mrt) { +rcu_read_unlock ();return ERR_PTR (-ENOENT ); + } iter->mrt = mrt; -rcu_read_lock ();return mr_vif_seq_start (seq, pos); }
Signature · 1 line
2.45.2
Paolo Abeni
maintainerRe: [PATCH net 1/3] ipmr: add debug check for mr table cleanup
In reply to Paolo Abeninet/ipv4/ipmr.c bool ipmr_rule_default(const struct fib_rule *rule) · line 363
Quoted 11 lines · Paolo Abeni
net/ipv4/ipmr.c +3
-0@@ -358,6 +358,11 @@ bool ipmr_rule_default(const struct fib_rule *rule)EXPORT_SYMBOL (ipmr_rule_default);#endif +static bool ipmr_can_free_table (struct net *net) +{ +return !check_net (net) || !net->ipv4.mr_rules_ops;
Not so good. mr_rules_ops exists only forCONFIG_IP_MROUTE_MULTIPLE_TABLES builds.
/P
kernel test robot
build botRe: [PATCH net 1/3] ipmr: add debug check for mr table cleanup
In reply to Paolo Abenikernel test robot noticed the following build errors:
Hi Paolo,
kernel test robot noticed the following build errors:
[auto build test ERROR on net/main]
url: https://github.com/intel-lab-lkp/linux/commits/Paolo-Abeni/ipmr-add-debug-check-for-mr-table-cleanup/20241125-104108 base: net/main patch link: https://lore.kernel.org/r/23bd87600f046ce1f1c93513b6ac8f8152b22bf4.1732270911.git.pabeni%40redhat.com patch subject: [PATCH net 1/3] ipmr: add debug check for mr table cleanup config: sh-se7712_defconfig (https://download.01.org/0day-ci/archive/20241125/[email protected]/config) compiler: sh4-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241125/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>):
net/ipv4/ipmr.c: In function 'ipmr_can_free_table':
>> net/ipv4/ipmr.c:363:46: error: 'struct netns_ipv4' has no member named 'mr_rules_ops'; did you mean 'rules_ops'?
363 | return !check_net(net) || !net->ipv4.mr_rules_ops;
| ^~~~~~~~~~~~
| rules_opsvim +363 net/ipv4/ipmr.c
360
361 static bool ipmr_can_free_table(struct net *net)
362 {
> 363 return !check_net(net) || !net->ipv4.mr_rules_ops;
364 }
365
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
kernel test robot
build botRe: [PATCH net 1/3] ipmr: add debug check for mr table cleanup
In reply to Paolo Abenikernel test robot noticed the following build errors:
Hi Paolo,
kernel test robot noticed the following build errors:
[auto build test ERROR on net/main]
url: https://github.com/intel-lab-lkp/linux/commits/Paolo-Abeni/ipmr-add-debug-check-for-mr-table-cleanup/20241125-104108 base: net/main patch link: https://lore.kernel.org/r/23bd87600f046ce1f1c93513b6ac8f8152b22bf4.1732270911.git.pabeni%40redhat.com patch subject: [PATCH net 1/3] ipmr: add debug check for mr table cleanup config: mips-ip22_defconfig (https://download.01.org/0day-ci/archive/20241125/[email protected]/config) compiler: mips-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241125/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All errors (new ones prefixed by >>):
net/ipv6/ip6mr.c: In function 'ip6mr_can_free_table':
>> net/ipv6/ip6mr.c:346:46: error: 'struct netns_ipv6' has no member named 'mr6_rules_ops'; did you mean 'fib6_rules_ops'?
346 | return !check_net(net) || !net->ipv6.mr6_rules_ops;
| ^~~~~~~~~~~~~
| fib6_rules_opsvim +346 net/ipv6/ip6mr.c
343
344 static bool ip6mr_can_free_table(struct net *net)
345 {
> 346 return !check_net(net) || !net->ipv6.mr6_rules_ops;
347 }
348
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
kernel test robot
build botRe: [PATCH net 1/3] ipmr: add debug check for mr table cleanup
In reply to Paolo Abenikernel test robot noticed the following build warnings:
Hi Paolo,
kernel test robot noticed the following build warnings:
[auto build test WARNING on net/main]
url: https://github.com/intel-lab-lkp/linux/commits/Paolo-Abeni/ipmr-add-debug-check-for-mr-table-cleanup/20241125-104108 base: net/main patch link: https://lore.kernel.org/r/23bd87600f046ce1f1c93513b6ac8f8152b22bf4.1732270911.git.pabeni%40redhat.com patch subject: [PATCH net 1/3] ipmr: add debug check for mr table cleanup config: arc-randconfig-001-20241126 (https://download.01.org/0day-ci/archive/20241126/[email protected]/config) compiler: arceb-elf-gcc (GCC) 13.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20241126/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
net/ipv4/ipmr.c: In function 'ipmr_can_free_table':
net/ipv4/ipmr.c:363:46: error: 'struct netns_ipv4' has no member named 'mr_rules_ops'; did you mean 'rules_ops'?
363 | return !check_net(net) || !net->ipv4.mr_rules_ops;
| ^~~~~~~~~~~~
| rules_ops
>> net/ipv4/ipmr.c:364:1: warning: control reaches end of non-void function [-Wreturn-type]
364 | }
| ^vim +364 net/ipv4/ipmr.c
360
361 static bool ipmr_can_free_table(struct net *net)
362 {
> 363 return !check_net(net) || !net->ipv4.mr_rules_ops; > 364 }
365
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki