Commit c6b41de4dd9f for kernel

commit c6b41de4dd9f2d6856d8d8d292348f7961034152
Author: Kyle Zeng <kylebot@openai.com>
Date:   Thu Oct 29 21:22:21 2026 -0700

    ip6mr: Serialize multicast router socket ownership

    MRT6_TABLE checks whether the socket owns its current table before
    acquiring RTNL. MRT6_INIT also looks up its table before taking RTNL.
    Consequently, concurrent TABLE and INIT calls can initialize the old
    table and then change the socket's selected table. Another INIT can
    register the same socket in the new table.

    rawv6_close() calls ip6mr_sk_done(), which clears only the first table
    owned by the socket. After the socket's RCU-delayed free, the other
    table retains a dangling mroute_sk pointer. A subsequent multicast cache
    report passes that pointer to sock_queue_rcv_skb_reason(). This produces
    a KASAN slab-use-after-free in sk_filter_trim_cap().

    Look up the selected table in ip6mr_sk_init() after acquiring RTNL.
    Likewise, re-read the current table and check its owner under RTNL in
    MRT6_TABLE before updating the selection. This preserves the single-table
    ownership invariant shared with ip6mr_sk_done(), without changing the
    locking of the other multicast socket options.

    Fixes: d1db275dd3f6 ("ipv6: ip6mr: support multiple tables")
    Signed-off-by: Kyle Zeng <kylebot@openai.com>
    Reviewed-by: Ido Schimmel <idosch@nvidia.com>
    Link: https://patch.msgid.link/20261030042221.73996-1-kylebot@openai.com
    Signed-off-by: Paolo Abeni <pabeni@redhat.com>

diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c
index 9710e8958b38..dd0638d3c3f9 100644
--- a/net/ipv6/ip6mr.c
+++ b/net/ipv6/ip6mr.c
@@ -1622,12 +1622,20 @@ 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 sock *sk)
 {
-	int err = 0;
 	struct net *net = sock_net(sk);
+	struct mr_table *mrt;
+	int err = 0;

 	rtnl_lock();
+	mrt = __ip6mr_get_table(net,
+				raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
+	if (!mrt) {
+		err = -ENOENT;
+		goto out_unlock;
+	}
+
 	spin_lock(&mrt_lock);
 	if (rtnl_dereference(mrt->mroute_sk)) {
 		err = -EADDRINUSE;
@@ -1643,6 +1651,7 @@ static int ip6mr_sk_init(struct mr_table *mrt, struct sock *sk)
 					     NETCONFA_MC_FORWARDING,
 					     NETCONFA_IFINDEX_ALL,
 					     net->ipv6.devconf_all);
+out_unlock:
 	rtnl_unlock();

 	return err;
@@ -1744,7 +1753,7 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 		if (optlen < sizeof(int))
 			return -EINVAL;

-		return ip6mr_sk_init(mrt, sk);
+		return ip6mr_sk_init(sk);

 	case MRT6_DONE:
 		return ip6mr_sk_done(sk);
@@ -1871,16 +1880,21 @@ 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))
-			return -EBUSY;
-
 		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;
+		mrt = __ip6mr_get_table(net,
+					raw6_sk(sk)->ip6mr_table ? : RT6_TABLE_DFLT);
+		if (!mrt) {
+			ret = -ENOENT;
+		} else if (sk == rtnl_dereference(mrt->mroute_sk)) {
+			ret = -EBUSY;
+		} else {
+			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;
 	}