Re: [PATCH] mm/rmap: Add anon_vma lifetime debug check

From: David Hildenbrand
Date: Fri Jul 25 2025 - 07:12:25 EST


On 25.07.25 13:08, Jann Horn wrote:
On Thu, Jul 24, 2025 at 11:56 PM David Hildenbrand <david@xxxxxxxxxx> wrote:
On 24.07.25 21:13, Jann Horn wrote:
If an anon page is mapped into userspace, its anon_vma must be alive,
otherwise rmap walks can hit UAF.

There have been syzkaller reports a few months ago[1][2] of UAF in rmap
walks that seems to indicate that there can be pages with elevated mapcount
whose anon_vma has already been freed, but I think we never figured out
what the cause is; and syzkaller only hit these UAFs when memory pressure
randomly caused reclaim to rmap-walk the affected pages, so it of course
didn't manage to create a reproducer.

Add a VM_WARN_ON_FOLIO() when we add/remove mappings of anonymous pages to
hopefully catch such issues more reliably.

Implementation note: I'm checking IS_ENABLED(CONFIG_DEBUG_VM) because,
unlike the checks above, this one would otherwise be hard to write such
that it completely compiles away in non-debug builds by itself, without
looking extremely ugly.

[1] https://lore.kernel.org/r/67abaeaf.050a0220.110943.0041.GAE@xxxxxxxxxx
[2] https://lore.kernel.org/r/67a76f33.050a0220.3d72c.0028.GAE@xxxxxxxxxx

Signed-off-by: Jann Horn <jannh@xxxxxxxxxx>
---
include/linux/rmap.h | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/include/linux/rmap.h b/include/linux/rmap.h
index c4f4903b1088..ba694c436f59 100644
--- a/include/linux/rmap.h
+++ b/include/linux/rmap.h
@@ -449,6 +449,19 @@ static inline void __folio_rmap_sanity_checks(const struct folio *folio,
default:
VM_WARN_ON_ONCE(true);
}
+
+ /*
+ * Anon folios must have an associated live anon_vma as long as they're
+ * mapped into userspace.
+ * Part of the purpose of the atomic_read() is to make KASAN check that
+ * the anon_vma is still alive.
+ */
+ if (IS_ENABLED(CONFIG_DEBUG_VM) && PageAnonNotKsm(page)) {

1) You probably don't need the CONFIG_DEBUG_VM check: the
VM_WARN_ON_FOLIO should make everything get optimized out ... right?

The PageAnonNotKsm() check is outside the VM_WARN_ON_FOLIO(), and it
uses page_folio(), which uses _compound_head(), which does
READ_ONCE(page->compound_head); and READ_ONCE() unfortunately doesn't
just mean "I want a read without tearing", it also (intentionally)
prevents the compiler from removing the read when it sees that it's
not being used for anything.

That makes sense, the volatile read will not get optimized out.


2) We have a folio here, so ... better

if (folio_test_anon(folio) && !folio_test_ksm(folio)) {
...
}

Hrm, okay. It kind of irks me to write it as two checks when really I
want to ask "is it this one specific type", but yeah, will change it.

Well, ksm is a subtype of anon.

folio_test_anon_not_ksm()

let's rather ... not :)

--
Cheers,

David / dhildenb