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.

Reviewed-by: David Ahern <dsahern@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2024-11-24 16:40:56 +01:00
parent 663a917475
commit 11b6e701bc
2 changed files with 28 additions and 0 deletions

View File

@ -120,6 +120,11 @@ static void ipmr_expire_process(struct timer_list *t);
lockdep_rtnl_is_held() || \ lockdep_rtnl_is_held() || \
list_empty(&net->ipv4.mr_tables)) list_empty(&net->ipv4.mr_tables))
static bool ipmr_can_free_table(struct net *net)
{
return !check_net(net) || !net->ipv4.mr_rules_ops;
}
static struct mr_table *ipmr_mr_table_iter(struct net *net, static struct mr_table *ipmr_mr_table_iter(struct net *net,
struct mr_table *mrt) struct mr_table *mrt)
{ {
@ -302,6 +307,11 @@ EXPORT_SYMBOL(ipmr_rule_default);
#define ipmr_for_each_table(mrt, net) \ #define ipmr_for_each_table(mrt, net) \
for (mrt = net->ipv4.mrt; mrt; mrt = NULL) for (mrt = net->ipv4.mrt; mrt; mrt = NULL)
static bool ipmr_can_free_table(struct net *net)
{
return !check_net(net);
}
static struct mr_table *ipmr_mr_table_iter(struct net *net, static struct mr_table *ipmr_mr_table_iter(struct net *net,
struct mr_table *mrt) struct mr_table *mrt)
{ {
@ -413,6 +423,10 @@ static struct mr_table *ipmr_new_table(struct net *net, u32 id)
static void ipmr_free_table(struct mr_table *mrt) 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); timer_shutdown_sync(&mrt->ipmr_expire_timer);
mroute_clean_tables(mrt, MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC | mroute_clean_tables(mrt, MRT_FLUSH_VIFS | MRT_FLUSH_VIFS_STATIC |
MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC); MRT_FLUSH_MFC | MRT_FLUSH_MFC_STATIC);

View File

@ -108,6 +108,11 @@ static void ipmr_expire_process(struct timer_list *t);
lockdep_rtnl_is_held() || \ lockdep_rtnl_is_held() || \
list_empty(&net->ipv6.mr6_tables)) list_empty(&net->ipv6.mr6_tables))
static bool ip6mr_can_free_table(struct net *net)
{
return !check_net(net) || !net->ipv6.mr6_rules_ops;
}
static struct mr_table *ip6mr_mr_table_iter(struct net *net, static struct mr_table *ip6mr_mr_table_iter(struct net *net,
struct mr_table *mrt) struct mr_table *mrt)
{ {
@ -291,6 +296,11 @@ EXPORT_SYMBOL(ip6mr_rule_default);
#define ip6mr_for_each_table(mrt, net) \ #define ip6mr_for_each_table(mrt, net) \
for (mrt = net->ipv6.mrt6; mrt; mrt = NULL) for (mrt = net->ipv6.mrt6; mrt; mrt = NULL)
static bool ip6mr_can_free_table(struct net *net)
{
return !check_net(net);
}
static struct mr_table *ip6mr_mr_table_iter(struct net *net, static struct mr_table *ip6mr_mr_table_iter(struct net *net,
struct mr_table *mrt) struct mr_table *mrt)
{ {
@ -392,6 +402,10 @@ static struct mr_table *ip6mr_new_table(struct net *net, u32 id)
static void ip6mr_free_table(struct mr_table *mrt) 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); timer_shutdown_sync(&mrt->ipmr_expire_timer);
mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC | mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC |
MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC); MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC);