Re: [PATCH 1/2] mm: kmsan: fix iounmap metadata teardown

From: Alexander Potapenko

Date: Tue Sep 29 2026 - 09:43:31 EST


On Tue, Sep 15, 2026 at 9:02 PM Dima Koziuk <dmytrokoziuk68@xxxxxxxxx> wrote:

Hi, sorry for this taking so long.

>
> While studying the code, I noticed that kmsan_iounmap_page_range() calls
> __vunmap_range_noflush(v_shadow, vmalloc_shadow(end)) inside its per-page
> loop, and does the same for origin. The first iteration therefore unmaps
> the entire metadata range, removing the PTEs for later pages before the
> loop can recover their backing pages.
>
> Looking at the page lookup, I found another problem. The only callers of
> kmsan_vmalloc_to_page_or_null() pass shadow and origin addresses, but the
> helper accepts only regular vmalloc and module addresses. KMSAN metadata
> lies outside those ranges, so the helper returns NULL and the backing
> blocks are never freed.

My bad, kmsan_vmalloc_to_page_or_null() is indeed bogus.
But what if we factor out the page walking part of vmalloc_to_page()
into a separate helper, so that it looks like:

struct page *vmalloc_to_page(const void *vmalloc_addr)
{
/*
* XXX we might need to change this if we add VIRTUAL_BUG_ON for
* architectures that do not vmalloc module space
*/
VIRTUAL_BUG_ON(!is_vmalloc_or_module_addr(vmalloc_addr));
return __vmalloc_to_page(vmalloc_addr);
}
EXPORT_SYMBOL(vmalloc_to_page);

Then kmsan_vmalloc_to_page_or_null() will start working:

struct page *kmsan_vmalloc_to_page_or_null(void *vaddr)
{
struct page *page;

page = __vmalloc_to_page(vaddr);
if (page && pfn_valid(page_to_pfn(page)))
return page;
else
return NULL;
}

, and we'll only need to fix kmsan_iounmap_pages() to vunmap the pages once:

static void kmsan_iounmap_pages(unsigned long start, unsigned long end)
{
unsigned long shadow_start = vmalloc_shadow(start),
shadow_end = vmalloc_shadow(end);
unsigned long origin_start = vmalloc_origin(start),
origin_end = vmalloc_origin(end);
unsigned long v_shadow, v_origin;
struct page *shadow, *origin;
int nr;

nr = (end - start) / PAGE_SIZE;
v_shadow = shadow_start;
v_origin = origin_start;
for (int i = 0; i < nr;
i++, v_shadow += PAGE_SIZE, v_origin += PAGE_SIZE) {
shadow = kmsan_vmalloc_to_page_or_null((void *)v_shadow);
origin = kmsan_vmalloc_to_page_or_null((void *)v_origin);
if (shadow)
__free_pages(shadow, KMSAN_IOREMAP_META_ORDER);
if (origin)
__free_pages(origin, KMSAN_IOREMAP_META_ORDER);
}
__vunmap_range_noflush(shadow_start, shadow_end);
__vunmap_range_noflush(origin_start, origin_end);
flush_tlb_kernel_range(shadow_start, shadow_end);
flush_tlb_kernel_range(origin_start, origin_end);
}

I think this is cleaner than reimplementing the page walk exclusively for KMSAN.