[PATCH mptcp-net v4] mptcp: pm: userspace: fix entry free path via RCU callback

Geliang Tang posted 1 patch 5 days, 1 hour ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/multipath-tcp/mptcp_net-next tags/patchew/97963911be08f2b6f99835217a24d23a99f3ac1f.1789983879.git.tanggeliang@kylinos.cn
net/mptcp/pm_userspace.c | 26 ++++++++++++++++++++------
net/mptcp/protocol.h     |  6 +++++-
2 files changed, 25 insertions(+), 7 deletions(-)
[PATCH mptcp-net v4] mptcp: pm: userspace: fix entry free path via RCU callback
Posted by Geliang Tang 5 days, 1 hour ago
From: Geliang Tang <tanggeliang@kylinos.cn>

In mptcp_pm_nl_remove_doit(), sk_omem_alloc is decremented immediately
but the entry memory is freed later via kfree_rcu_mightsleep(). A
CAP_NET_ADMIN user can bypass the socket memory quota and exhaust kernel
memory by accumulating pending RCU callbacks.

Fix by deferring both the memory free and the sk_omem_alloc adjustment to
an RCU callback via sock_kfree_s().

A new mptcp_userspace_pm_release_entry() helper takes a sock reference
with sock_hold() before scheduling the callback, and the callback releases
it with sock_put() after freeing.

Apply the same helper to mptcp_userspace_pm_delete_local_addr() which has
the same issue. The synchronous freeing in free_local_addr_list() is left
unchanged: it only runs during msk destruction where no concurrent readers
exist.

While at it, reuse the lsk field of mptcp_pm_addr_entry via an anonymous
union for the sock backpointer - lsk is only used by the kernel PM, sk by
the userspace PM, so they are mutually exclusive.

Fixes: 13b4ece33cf9 ("mptcp: pm: Defer freeing of MPTCP userspace path manager entries")
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
v4:
 - address Mat & Matt's review comments on v3, thanks!
 - move sock_hold() and entry->sk assignment from allocation time to a new
   mptcp_userspace_pm_release_entry() helper at free time
 - keep free_local_addr_list() synchronous, no RCU needed there as it only
   runs during msk destruction
 - drop the SOCK_DEAD check in append_new_local_addr(), the leak chain is
   broken by the above sock_hold() relocation
 - use an anonymous union for lsk/sk instead of adding a new field

v3:
 - checking sock_flag(sk, SOCK_DEAD)) before holding the reference.
 - update the subject.
 - Link: https://patchwork.kernel.org/project/mptcp/patch/de8555fcb235d0e93c02c20bf47231846d30841d.1782886142.git.tanggeliang@kylinos.cn/

v2:
 - call mptcp_userspace_pm_free_entry in free_local_addr_list and
   delete_local_addr.
 - Link: https://patchwork.kernel.org/project/mptcp/patch/df199842d10185a73084c79aee9cdc91888adb6a.1782799160.git.tanggeliang@kylinos.cn/

v1:
 - Link: https://patchwork.kernel.org/project/mptcp/patch/9b443bafa57f40a51eb6a43f088ff37d71b39973.1782528088.git.tanggeliang@kylinos.cn/

This patch addresses the pre-existing issue Sashiko mentioned in
https://sashiko.dev/#/patchset/cover.1782457962.git.tanggeliang@kylinos.cn.
---
 net/mptcp/pm_userspace.c | 26 ++++++++++++++++++++------
 net/mptcp/protocol.h     |  6 +++++-
 2 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/net/mptcp/pm_userspace.c b/net/mptcp/pm_userspace.c
index f723a134356f..09c500c59eed 100644
--- a/net/mptcp/pm_userspace.c
+++ b/net/mptcp/pm_userspace.c
@@ -12,6 +12,24 @@
 	list_for_each_entry(__entry,						\
 			    &((__msk)->pm.userspace_pm_local_addr_list), list)
 
+static void mptcp_userspace_pm_free_entry(struct rcu_head *head)
+{
+	struct mptcp_pm_addr_entry *entry =
+		container_of(head, struct mptcp_pm_addr_entry, rcu);
+	struct sock *sk = entry->sk;
+
+	sock_kfree_s(sk, entry, sizeof(*entry));
+	sock_put(sk);
+}
+
+static void mptcp_userspace_pm_release_entry(struct mptcp_pm_addr_entry *entry,
+					     struct sock *sk)
+{
+	entry->sk = sk;
+	sock_hold(sk);
+	call_rcu(&entry->rcu, mptcp_userspace_pm_free_entry);
+}
+
 void mptcp_userspace_pm_free_local_addr_list(struct mptcp_sock *msk)
 {
 	struct mptcp_pm_addr_entry *entry, *tmp;
@@ -123,7 +141,7 @@ static int mptcp_userspace_pm_delete_local_addr(struct mptcp_sock *msk,
 	 * be used multiple times (e.g. fullmesh mode).
 	 */
 	list_del_rcu(&entry->list);
-	sock_kfree_s(sk, entry, sizeof(*entry));
+	mptcp_userspace_pm_release_entry(entry, sk);
 	msk->pm.local_addr_used--;
 	return 0;
 }
@@ -355,11 +373,7 @@ int mptcp_pm_nl_remove_doit(struct sk_buff *skb, struct genl_info *info)
 
 	release_sock(sk);
 
-	kfree_rcu_mightsleep(match);
-	/* Adjust sk_omem_alloc like sock_kfree_s() does, to match
-	 * with allocation of this memory by sock_kmemdup()
-	 */
-	atomic_sub(sizeof(*match), &sk->sk_omem_alloc);
+	mptcp_userspace_pm_release_entry(match, sk);
 
 	err = 0;
 out:
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 4bf04f9ecbd9..0724b90a4904 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -273,7 +273,11 @@ struct mptcp_pm_addr_entry {
 	struct mptcp_addr_info	addr;
 	u32			flags;
 	int			ifindex;
-	struct socket		*lsk;
+	union {
+		struct socket	*lsk;	/* kernel PM: listener socket */
+		struct sock	*sk;	/* userspace PM: owning msk */
+	};
+	struct rcu_head		rcu;
 };
 
 struct mptcp_data_frag {
-- 
2.53.0
Re: [PATCH mptcp-net v4] mptcp: pm: userspace: fix entry free path via RCU callback
Posted by MPTCP CI 5 days ago
Hi Geliang,

Thank you for your modifications, that's great!

Our CI did some validations and here is its report:

- KVM Validation: normal (except selftest_mptcp_join): Success! ✅
- KVM Validation: normal (only selftest_mptcp_join): Success! ✅
- KVM Validation: debug (except selftest_mptcp_join): Success! ✅
- KVM Validation: debug (only selftest_mptcp_join): Success! ✅
- KVM Validation: btf-normal (only bpftest_all): Success! ✅
- KVM Validation: btf-debug (only bpftest_all): Success! ✅
- Perf: Success! ✅
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/35587560715

Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/0f9d109fd7a7
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1170225


If there are some issues, you can reproduce them using the same environment as
the one used by the CI thanks to a docker image, e.g.:

    $ cd [kernel source code]
    $ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
        --pull always mptcp/mptcp-upstream-virtme-docker:latest \
        auto-normal

For more details:

    https://github.com/multipath-tcp/mptcp-upstream-virtme-docker


Please note that despite all the efforts that have been already done to have a
stable tests suite when executed on a public CI like here, it is possible some
reported issues are not due to your modifications. Still, do not hesitate to
help us improve that ;-)

Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)