Re: [PATCH v3 net-next 00/15] ip6mr: No RTNL for RTNL_FAMILY_IP6MR rtnetlink.

Kuniyuki Iwashima
2026-04-08T03:49:08Z

From: Jakub Kicinski <kuba@kernel.org>
Date: Tue, 7 Apr 2026 18:42:02 -0700
> On Tue, 7 Apr 2026 21:19:33 +0000 Kuniyuki Iwashima wrote:
> > This series is the IPv6 version of
> >
> > https://lore.kernel.org/netdev/20260228221800.1082070-1-kuniyu@google.com/
> >
> > and removes RTNL from ip6mr rtnetlink handlers.
>
> CI says:
>
> [ 857.648411][ T63] Oops: general protection fault, probably for non-canonical address 0xdffffc0000000006: 0000 [#1] SMP KASAN
> [ 857.648639][ T63] KASAN: null-ptr-deref in range [0x0000000000000030-0x0000000000000037]
> [ 857.648765][ T63] CPU: 2 UID: 0 PID: 63 Comm: kworker/2:2 Not tainted 7.0.0-rc6-virtme #1 PREEMPT(full)
> [ 857.648903][ T63] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011
> [ 857.649006][ T63] Workqueue: mld mld_ifc_work
> [ 857.649090][ T63] RIP: 0010:mroute6_is_socket+0x4a/0x80

Oops, CONFIG_IP_MROUTE_MULTIPLE_TABLES=n does not check
if net->ipv6.mrt6 is NULL and always returns 0.

Unlike ipmr, ip6mr had implicit dependency on the pernet ops
order to stop mld_ifc_work before ip6mr_rules_exit().

Before converting ->exit_batch() to ->exit_rtnl(), it must
prepare for that situation and properly use RCU helpers like below.

I'll replace patch 2 with the RCU conversion patch in v2.

( I noticed I marked this v3 by reusing the previous command for
ipv4 series, sorry for confusion. )

Thanks !

---8<---
diff --git a/include/linux/mroute_base.h b/include/linux/mroute_base.h
index 3341acca002f..ee68c05d0584 100644
--- a/include/linux/mroute_base.h
+++ b/include/linux/mroute_base.h
@@ -259,6 +259,7 @@ struct mr_table {
bool mroute_do_pim;
bool mroute_do_wrvifwhole;
int mroute_reg_vif_num;
+ struct rcu_head rcu;
};

static inline bool mr_can_free_table(struct net *net)
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index b9c048b6f1ca..468a30c76930 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -319,7 +319,9 @@ static struct mr_table *ip6mr_get_table(struct net *net, u32 id)
static int ip6mr_fib_lookup(struct net *net, struct flowi6 *flp6,
struct mr_table **mrt)
{
- *mrt = net->ipv6.mrt6;
+ *mrt = rcu_dereference(net->ipv6.mrt6);
+ if (!*mrt)
+ return -EAGAIN;
return 0;
}

@@ -338,7 +340,7 @@ static void __net_exit ip6mr_rules_exit(struct net *net)
{
ASSERT_RTNL();
ip6mr_free_table(net->ipv6.mrt6);
- net->ipv6.mrt6 = NULL;
+ RCU_INIT_POINTER(net->ipv6.mrt6, NULL);
}

static int ip6mr_rules_dump(struct net *net, struct notifier_block *nb,
@@ -412,7 +414,7 @@ static void ip6mr_free_table(struct mr_table *mrt)
mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MIFS_STATIC |
MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC);
rhltable_destroy(&mrt->mfc_hash);
- kfree(mrt);
+ kfree_rcu(mrt, rcu);
}

#ifdef CONFIG_PROC_FS
---8<---