Re: [RFC PATCH] mm/page_alloc: fix use-after-free in swap due to stale page data after split_page()

From: Mikhail Gavrilov

Date: Mon Feb 02 2026 - 00:27:24 EST


On Mon, Feb 2, 2026 at 8:18 AM Kairui Song <ryncsn@xxxxxxxxx> wrote:
>
> Right, but with __GFP_COMP, prep_compound_page clears the tail pages'
> private too, so the code snip I posted will clear their lru on use?

You're right that prep_compound_tail() clears page->private for tail
pages. But the issue is that vmalloc explicitly avoids __GFP_COMP -
see commit 3b8000ae185c ("mm/vmalloc: huge vmalloc backing pages
should be split rather than compound"). So prep_compound_page() is
never called for these allocations.

> I took a look at the history, commit 3b8000ae185c ("mm/vmalloc: huge
> vmalloc backing pages should be split rather than compound") dropped
> __GFP_COMP and added split_page, that's the commit added the comment
> you mentioned.

Good find! That's exactly where the problem was introduced.

> Fixing it with this patch you posted seems could result in other
> issues, e.g. split_free_pages / split_free_frozen_pages would call
> split_page while the page is on a list, so at least clearing head
> page's LRU seems incorrect?

You're absolutely right, that's a problem I missed. split_free_page()
calls split_page() on pages that are still on the buddy free list -
initializing head->lru there would corrupt the list.

So the fix should only initialize tail pages, not the head page:
/*
* Split pages may contain stale data from previous use. Initialize
* page->private and page->lru for tail pages which may have
* LIST_POISON values. Head page is left alone as callers like
* split_free_page() may have it on a list.
*/
for (i = 1; i < (1 << order); i++) {
set_page_refcounted(page + i);
set_page_private(page + i, 0);
INIT_LIST_HEAD(&page[i].lru);
}

But then we still have the problem for head page in vmalloc case.
Maybe the fix should be in vmalloc.c instead, after split_page()
returns?
Or alternatively, fix it in swapfile.c by unconditionally calling
INIT_LIST_HEAD() - the comment there is already wrong, so we should
fix both the comment and the code?
What do you think is the cleanest approach?

--
Best Regards,
Mike Gavrilov.