From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794454303; b=1MApUVGgKVP+bFN5Ml8MJGDOf9ZnL2zIPugaTlHhUfC/n+yba1+FIMXxZg8fzV0D2m4t dPTlcX5kPUUPx1jffIyxpDC0OZ8KlMVQFpW1DHUOiBisSAMUYdSzqUmzIZEFIL8fyhOsg dDmxjUB/VHe2EbVpjZw1MJusHvZyfXD/XV52lRxZLvNFLOdn8KiOZ+GbdEhOcKGwP1vyU RzyxE9k1isJoFjEikJtaSfwgyvju2HThK8UwIdrZqTRwYJk1mEiD4ncuhuA9E1KWRAYJm nvSgwQYh5k4cPDqJ+NYDDQZFHS4R4ti96GgNmLuwnkJAxglrF6wPOs2sTTReu8sGxVg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794454303; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:Content-Type: Content-Transfer-Encoding; bh=NN8MNqvRbtwzetrKQMbNySmwC2iLJRO+z3jcGK8mXgk=; b=Zx5VVGImXUHSi/TtLFssptC5AxwiDN6dMCd9OWxX6dSYkp9kYdz6fLtCIl+ovcbopRA6 xUJ/Dyz6rBb64T83G28/FfBRVa/UVOx0eelG0LIk2wsliZuG0V6hDZ5IQoxJ3rzyA5IlY VbAk16kDP9LGI/CfA+47jVx0Rc+HCyQUUtuh7qLk4uUuudXSs3pUbjrCNFYFPMkMkcIM+ BByvwUxC7N/C0ceYlYZt0RwsNz6oGmMXqmBe0xODlDCkjwwL6w4CvobERJfRHVmOC+xG8 Y6QMCwIvswh4l+S5oBEwUzXmZxvBcwg8+cBEHRQnDQKjkFxfyzy7RpKTXj6YSMqmldg== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=MqxF8qF+; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B674A606A321 for ; Thu, 12 Nov 2026 03:31:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=MqxF8qF+ Received: by smtp.kernel.org (Postfix) with ESMTPSA id D67EC1F00A06; Thu, 12 Nov 2026 03:31:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1794454303; bh=NN8MNqvRbtwzetrKQMbNySmwC2iLJRO+z3jcGK8mXgk=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=MqxF8qF+o8Zj11vpv0tkW57jaTuF13PFKnviQsJe6Sg1/2AyHy9E4DE7k7W8WBQj4 o5Z0kZXfScm8PpZ0tvGYwSeiG/W3qhLH/C98o+2I109Nv5Q/0gsmlWHBDv8F0922TE m54KEZWbLysw90gl1c/kTNbsE6BLV/prFoAhrEs3jdnT7NDafQ7sVL6gzMWUT0bgkj /EPvexzkqI6gJJfL4eRLW4KbLsETNUWK3bFN/6Oyh7a9gHLp1Y7vRU+lBjn5vxXEOO hqwCgJkj5l1Qu69zu9Rz25JePwj2a7GQYVfYfsryObiLo5vD89XFy9OQA9XLz3mSUj raBdjm5lCmwHQ== From: Sasha Levin To: stable@vger.kernel.org Cc: "David Hildenbrand (Arm)" , Chris Mason , "Lorenzo Stoakes (ARM)" , Xu Xin , Muhammad Usama Anjum , Sasha Levin Subject: [PATCH 6.1.y] mm/ksm: fix use of a freed anon_vma at swap-in Date: Wed, 11 Nov 2026 22:31:41 -0500 Message-ID: <20261112032126.1472370-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261109201846-failed-6.1-4c2dec1dce36@kernel.org> References: <20261109201846-failed-6.1-4c2dec1dce36@kernel.org> X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From: "David Hildenbrand (Arm)" [ Upstream commit 4c2dec1dce368ad9e9b1814307cb5ef3832fa07e ] A folio read from a swap slot previously shared by KSM can remain in the swap cache after the process that first mapped it exits. Its anon_vma can then be freed, so ksm_might_need_to_copy() must not dereference it to compare roots. Doing so can result in some false-positives, resulting in the reverse mapping missing the new folio mapping, preventing migration and reclaim. SLAB_TYPESAFE_BY_RCU allows individual anon_vma objects to be reused immediately after freeing. A stale pointer can then refer to another anon_vma whose root matches, even though the target VMA is not linked to that anon_vma. Move the anonymous rmap compatibility checks into folio_anon_rmap_compatible(). Keep the page-index check and compare the folio's anon_vma pointer against the VMA's own anon_vma and root first. If neither matches, bite the bullet and walk the VMA's anon_vma_chain. The VMA keeps these anon_vmas alive, and finding a match guarantees that reverse mapping can find the new mapping. Lorenzo Stoakes says in a private discussion: "This isn't quite equivalent to the existing check. Every anon_vma in an anon_vma_chain shares the same root, but if you split a VMA then fork both of them like this: VMA A / \ VMA B VMA C \ / root <- anon_vma 1 ----^ ^---- / parent \ anon_vma 2 anon_vma 3 | | VMA D VMA E VMA's D and E both get their own new anon_vma's such that: VMA D: anon_vma 2 -> anon_vma 1 -> root VMA E: anon_vma 3 -> anon_vma 1 -> root And if you found a folio referencing anon_vma 2, the existing check: anon_vma->root == vma->anon-vma->root Would be true for VMA's B, C, D & E. However after your change it'd be true for VMA D only, as it's the only one with anon_vma 2 in its anon_vma_chain. Whether or not that could actually happen in practice... 🙂 I don't know. [...] after your change it'd be true for VMA D only, as it's the only one with anon_vma 2 in its anon_vma_chain. [...] I would [...] say that you are making the test stricter but more correct. " If this affects performance when KSM was never even involved, we could just track whether KSM ever merged anything and just skip these checks. There isn't really a lot we can do otherwise: for mapped folios we might be able to use folio_get_anon_vma(), but for unmapped folios there really is no easy way to see if the anon_vma is still valid. Document the (confusing) lifetime requirements of folio_anon_vma(). Fixes: ba6f0ff3981e ("ksm: fix ksm swapin time optimization") Cc: stable@vger.kernel.org Reported-by: Chris Mason Reviewed-by: Lorenzo Stoakes (ARM) Acked-by: Xu Xin Tested-by: Muhammad Usama Anjum Link: https://patch.msgid.link/20261031-fixes_core-v3-40-d6437171ecc3@kernel.org Signed-off-by: David Hildenbrand (Arm) [ sashal: inlined folio_anon_rmap_compatible() logic using the existing page interface and page index checks ] Signed-off-by: Sasha Levin --- mm/ksm.c | 16 +++++++++++++--- mm/util.c | 13 +++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/mm/ksm.c b/mm/ksm.c index 99eb19860fd59..5be08cb1603a7 100644 --- a/mm/ksm.c +++ b/mm/ksm.c @@ -2688,6 +2688,7 @@ struct page *ksm_might_need_to_copy(struct page *page, { struct folio *folio = page_folio(page); struct anon_vma *anon_vma = folio_anon_vma(folio); + struct anon_vma_chain *avc; struct page *new_page; if (PageKsm(page)) { @@ -2696,9 +2697,18 @@ struct page *ksm_might_need_to_copy(struct page *page, return page; /* no need to copy it */ } else if (!anon_vma) { return page; /* no need to copy it */ - } else if (page->index == linear_page_index(vma, address) && - anon_vma->root == vma->anon_vma->root) { - return page; /* still no need to copy it */ + } else if (page->index == linear_page_index(vma, address)) { + /* + * Careful: anon_vma might have been freed, so we cannot safely + * dereference it. Add a fast path before we walk the anon_vma chain. + */ + if (anon_vma == vma->anon_vma || anon_vma == vma->anon_vma->root) + return page; /* still no need to copy it */ + + list_for_each_entry(avc, &vma->anon_vma_chain, same_vma) { + if (avc->anon_vma == anon_vma) + return page; + } } if (!PageUptodate(page)) return page; /* let do_swap_page report the error */ diff --git a/mm/util.c b/mm/util.c index d3a2877c176f9..2187074f4ca6c 100644 --- a/mm/util.c +++ b/mm/util.c @@ -743,6 +743,19 @@ bool folio_mapped(struct folio *folio) } EXPORT_SYMBOL(folio_mapped); +/** + * folio_anon_vma - Get a folio's raw anon_vma pointer. + * @folio: The folio. + * + * The returned pointer may be stale for an unmapped folio. The caller + * must ensure the anon_vma stays alive before dereferencing it. A folio + * reference or lock alone is insufficient; use folio_get_anon_vma() when + * the anon_vma's lifetime is not otherwise protected. Note that + * folio_get_anon_vma() is only useful for folios that are still mapped. + * + * Return: The raw anon_vma pointer for an anonymous, non-KSM folio, or + * %NULL otherwise. + */ struct anon_vma *folio_anon_vma(struct folio *folio) { unsigned long mapping = (unsigned long)folio->mapping; -- 2.53.0