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.
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.