* [PATCH] LoongArch: add KASAN_VMALLOC support @ 2026-10-12 8:04 haoran.jiang 2026-10-14 4:43 ` Huacai Chen 0 siblings, 1 reply; 8+ messages in thread From: haoran.jiang @ 2026-10-12 8:04 UTC (permalink / raw) To: loongarch; +Cc: kasan-dev, linux-kernel, chenhuacai, kernel, Haoran Jiang From: Haoran Jiang <jianghaoran@kylinos.cn> For the page table of the VMALLOC shadow region, only a partial page table needs to be established, allowing the complete page table entries to be populated on demand during use. --- 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, page tables are established for the modules and vmalloc regions, but the page tables are not fully populated; the complete page tables are established automatically when vmalloc is used. If the address is pgd-aligned, the page table is established down to the pgd level; if it is pud-aligned, it is established down to the pud level, and so on. While establishing the /pgd/pud/pmd levels, the page table entries must be initialized to none. For example, a pmd entry of none is an invalid_pte_table. Only in this way can the correct page tables be established at use time. At the same time, boundary issues must be considered when the address is not aligned. For example, mem_to_shadow(MALLOC_END) and mem_to_shadow(vmemmap) are in the same pmd, and the region [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). Other architectures such as arm64/riscv need kasan_populate_early_shadow because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before kasan_populate_early_shadow, so the reserved region must be mapped to the zero page again. On LoongArch, however, the entire shadow space is first mapped to page 0 via kasan_pgd_populate, and then page tables are established for the required regions, which is simpler and clearer. 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively because kasan_populate_early_vm_area_shadow requires them. kasan_populate_early_vm_area_shadow establishes complete KASAN shadow page table mappings for the early vmalloc regions before vmalloc_init. At this point, complete page tables need to be established, but kasan_populate_early_vm_area_shadow is called after step 1, at which time only incomplete page tables have been established for the vmalloc region. The condition for a page table entry to be none requires the pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining whether an entry is none can the complete page tables be established properly. Test: https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git CONFIG_KUNIT=y CONFIG_KASAN_KUNIT_TEST=m Add `kunit.enable=1 kasan_multi_shot` to the cmdline. Test with 3-level 16K pages and 4-level 4K pages respectively. before this patch: ... [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y ... [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 [ 504.016171] ok 1 kasan after this patch: ... [ 531.630760] ok 68 vmalloc_oob ... [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 [ 532.891219] ok 1 kasan Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> --- arch/loongarch/Kconfig | 2 + arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- 2 files changed, 164 insertions(+), 11 deletions(-) diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig index 2067d1f2ad7a..c95c6fd8a138 100644 --- a/arch/loongarch/Kconfig +++ b/arch/loongarch/Kconfig @@ -119,6 +119,7 @@ config LOONGARCH select HAVE_ARCH_JUMP_LABEL select HAVE_ARCH_JUMP_LABEL_RELATIVE select HAVE_ARCH_KASAN if 64BIT + select HAVE_ARCH_KASAN_VMALLOC select HAVE_ARCH_KFENCE if 64BIT select HAVE_ARCH_KGDB if PERF_EVENTS select HAVE_ARCH_KSTACK_ERASE @@ -187,6 +188,7 @@ config LOONGARCH select HOTPLUG_SMT if HOTPLUG_CPU select IRQ_FORCED_THREADING select IRQ_LOONGARCH_CPU + select KASAN_VMALLOC if KASAN select LOCK_MM_AND_FIND_VMA select MMU_GATHER_MERGE_VMAS if MMU select MMU_GATHER_RCU_TABLE_FREE diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c index 92ca0ba86406..962c37316200 100644 --- a/arch/loongarch/mm/kasan_init.c +++ b/arch/loongarch/mm/kasan_init.c @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); #define __pgd_none(early, pgd) (0) #else #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) #endif #ifdef __PAGETABLE_PUD_FOLDED #define __p4d_none(early, p4d) (0) #else #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) #endif #ifdef __PAGETABLE_PMD_FOLDED #define __pud_none(early, pud) (0) #else #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) #endif #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) #define __pte_none(early, pte) (early ? pte_none(pte) : \ -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) static void *mem_to_shadow(const void *addr) { @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); } +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, + unsigned long end, int node) +{ + unsigned long next; + pte_t *ptep = pte_offset_kernel(pmdp, addr); + + do { + next = addr + PAGE_SIZE; + if (__pte_none(false, ptep_get(ptep))) + pte_clear(&init_mm, addr, ptep); + } while (ptep++, addr = next, addr != end); +} + +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, + unsigned long end, int node) +{ + unsigned long next; + pmd_t *pmdp = pmd_offset(pudp, addr); + + do { + next = pmd_addr_end(addr, end); + + if (__pmd_none(false, pmdp_get(pmdp))) { + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); + + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { + kernel_pte_init(__va(pte_phys)); + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); + continue; + } else { + memcpy(__va(pte_phys), kasan_early_shadow_pte, + sizeof(kasan_early_shadow_pte)); + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); + } + } + kasan_shallow_populate_pte(pmdp, addr, next, node); + } while (pmdp++, addr = next, addr != end); +} + +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, + unsigned long end, int node) +{ + unsigned long next; + pud_t *pudp = pud_offset(p4dp, addr); + + do { + next = pud_addr_end(addr, end); + + if (__pud_none(false, pudp_get(pudp))) { + + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); + + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { +#ifndef __PAGETABLE_PMD_FOLDED + pmd_init(__va(pmd_phys)); +#endif + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); + continue; + } else { + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, + sizeof(kasan_early_shadow_pmd)); + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); + + } + } + + kasan_shallow_populate_pmd(pudp, addr, next, node); + } while (pudp++, addr = next, addr != end); +} + +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, + unsigned long end, int node) +{ + unsigned long next; + p4d_t *p4dp = p4d_offset(pgdp, addr); + + do { + next = p4d_addr_end(addr, end); + + if (__p4d_none(false, p4dp_get(p4dp))) { + + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); + + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { +#ifndef __PAGETABLE_PUD_FOLDED + pud_init(__va(pud_phys)); +#endif + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); + continue; + } else { + memcpy(__va(pud_phys), kasan_early_shadow_pud, + sizeof(kasan_early_shadow_pud)); + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); + } + } + kasan_shallow_populate_pud(p4dp, addr, next, node); + } while (p4dp++, addr = next, addr != end); +} + +static void __init kasan_shallow_populate_pgd(unsigned long addr, + unsigned long end, int node) +{ + unsigned long next; + pgd_t *pgdp; + + pgdp = pgd_offset_k(addr); + + do { + next = pgd_addr_end(addr, end); + + if (__pgd_none(false, pgdp_get(pgdp))) { + + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); + + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { + pgd_init(__va(p4d_phys)); + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); + continue; + } else { + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, + sizeof(kasan_early_shadow_p4d)); + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); + } + } + + kasan_shallow_populate_p4d(pgdp, addr, next, node); + } while (pgdp++, addr = next, addr != end); +} + +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) +{ + unsigned long vaddr = (unsigned long)start & PAGE_MASK; + unsigned long vend = PAGE_ALIGN((unsigned long)end); + + kasan_shallow_populate_pgd(vaddr, vend, node); +} + +#ifdef CONFIG_KASAN_VMALLOC +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) +{ + unsigned long shadow_start, shadow_end; + + if (!is_vmalloc_or_module_addr(start)) + return; + + shadow_start = (unsigned long)mem_to_shadow(start); + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); + shadow_end = (unsigned long)mem_to_shadow(start + size); + shadow_end = ALIGN(shadow_end, PAGE_SIZE); + + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); +} +#endif + asmlinkage void __init kasan_early_init(void) { BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); @@ -297,9 +451,6 @@ void __init kasan_init(void) /* Maps everything to a single page of zeroes */ kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), - mem_to_shadow((void *)KFENCE_AREA_END)); - /* Populate the linear mapping */ for_each_mem_range(i, &pa_start, &pa_end) { void *start = (void *)phys_to_virt(pa_start); @@ -309,9 +460,9 @@ void __init kasan_init(void) (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); } - /* Populate modules mapping */ - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); + /* * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we * should make sure that it maps the zero page read-only. -- 2.43.0 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-12 8:04 [PATCH] LoongArch: add KASAN_VMALLOC support haoran.jiang @ 2026-10-14 4:43 ` Huacai Chen 2026-10-16 15:04 ` haoran.jiang 0 siblings, 1 reply; 8+ messages in thread From: Huacai Chen @ 2026-10-14 4:43 UTC (permalink / raw) To: haoran.jiang; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang Hi, Haoran, On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > For the page table of the VMALLOC shadow region, > only a partial page table needs to be established, > allowing the complete page table entries to be > populated on demand during use. > > --- > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > page tables are established for the modules and vmalloc regions, > but the page tables are not fully populated; the complete page > tables are established automatically when vmalloc is used. Why also change modules together with vmalloc regions? Because they can both alloc/free dynamically, and the current approch can only handle "one time" for modules? > If the address is pgd-aligned, the page table is established > down to the pgd level; if it is pud-aligned, it is established > down to the pud level, and so on. While establishing the > /pgd/pud/pmd levels, the page table entries must be initialized > to none. For example, a pmd entry of none is an invalid_pte_table. > Only in this way can the correct page tables be established at > use time. At the same time, boundary issues must be considered > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > and mem_to_shadow(vmemmap) are in the same pmd, and the region > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. kasan_pgd_populate(early) populate the page table to early shadow page, kasan_pgd_populate(!early) populate the page table to normal shadow page, and kasan_shallow_populate() populate the page table to invalid_{pud,pmd,pte}_table? It seems you are do similar work as RISCV, and RISCV use three APIs for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch use the same API for the 1st and 2nd purpose, can we add a "shallow" parameter to kasan_pgd_populate() so we can also handle the 3th purpose? > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > Other architectures such as arm64/riscv need kasan_populate_early_shadow > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > page again. On LoongArch, however, the entire shadow space is first mapped to > page 0 via kasan_pgd_populate, and then page tables are established for the > required regions, which is simpler and clearer. > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > because kasan_populate_early_vm_area_shadow requires them. > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > page table mappings for the early vmalloc regions before vmalloc_init. > At this point, complete page tables need to be established, but > kasan_populate_early_vm_area_shadow is called after step 1, at which time I'm a little confusing here. What is "step 1" here? > only incomplete page tables have been established for the vmalloc region. > The condition for a page table entry to be none requires the > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > whether an entry is none can the complete page tables be established properly. > > Test: > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > CONFIG_KUNIT=y > > CONFIG_KASAN_KUNIT_TEST=m > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > before this patch: > > ... > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > ... > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > [ 504.016171] ok 1 kasan > > after this patch: > > ... > > [ 531.630760] ok 68 vmalloc_oob > > ... > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > [ 532.891219] ok 1 kasan > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > --- > arch/loongarch/Kconfig | 2 + > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > 2 files changed, 164 insertions(+), 11 deletions(-) > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > index 2067d1f2ad7a..c95c6fd8a138 100644 > --- a/arch/loongarch/Kconfig > +++ b/arch/loongarch/Kconfig > @@ -119,6 +119,7 @@ config LOONGARCH > select HAVE_ARCH_JUMP_LABEL > select HAVE_ARCH_JUMP_LABEL_RELATIVE > select HAVE_ARCH_KASAN if 64BIT > + select HAVE_ARCH_KASAN_VMALLOC I think this should be limited by 64BIT. Huacai > select HAVE_ARCH_KFENCE if 64BIT > select HAVE_ARCH_KGDB if PERF_EVENTS > select HAVE_ARCH_KSTACK_ERASE > @@ -187,6 +188,7 @@ config LOONGARCH > select HOTPLUG_SMT if HOTPLUG_CPU > select IRQ_FORCED_THREADING > select IRQ_LOONGARCH_CPU > + select KASAN_VMALLOC if KASAN > select LOCK_MM_AND_FIND_VMA > select MMU_GATHER_MERGE_VMAS if MMU > select MMU_GATHER_RCU_TABLE_FREE > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > index 92ca0ba86406..962c37316200 100644 > --- a/arch/loongarch/mm/kasan_init.c > +++ b/arch/loongarch/mm/kasan_init.c > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > #define __pgd_none(early, pgd) (0) > #else > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > #endif > > #ifdef __PAGETABLE_PUD_FOLDED > #define __p4d_none(early, p4d) (0) > #else > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > #endif > > #ifdef __PAGETABLE_PMD_FOLDED > #define __pud_none(early, pud) (0) > #else > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > #endif > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > static void *mem_to_shadow(const void *addr) > { > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > } > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > + unsigned long end, int node) > +{ > + unsigned long next; > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > + > + do { > + next = addr + PAGE_SIZE; > + if (__pte_none(false, ptep_get(ptep))) > + pte_clear(&init_mm, addr, ptep); > + } while (ptep++, addr = next, addr != end); > +} > + > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > + unsigned long end, int node) > +{ > + unsigned long next; > + pmd_t *pmdp = pmd_offset(pudp, addr); > + > + do { > + next = pmd_addr_end(addr, end); > + > + if (__pmd_none(false, pmdp_get(pmdp))) { > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > + > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > + kernel_pte_init(__va(pte_phys)); > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > + continue; > + } else { > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > + sizeof(kasan_early_shadow_pte)); > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > + } > + } > + kasan_shallow_populate_pte(pmdp, addr, next, node); > + } while (pmdp++, addr = next, addr != end); > +} > + > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > + unsigned long end, int node) > +{ > + unsigned long next; > + pud_t *pudp = pud_offset(p4dp, addr); > + > + do { > + next = pud_addr_end(addr, end); > + > + if (__pud_none(false, pudp_get(pudp))) { > + > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > + > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > +#ifndef __PAGETABLE_PMD_FOLDED > + pmd_init(__va(pmd_phys)); > +#endif > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > + continue; > + } else { > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > + sizeof(kasan_early_shadow_pmd)); > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > + > + } > + } > + > + kasan_shallow_populate_pmd(pudp, addr, next, node); > + } while (pudp++, addr = next, addr != end); > +} > + > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > + unsigned long end, int node) > +{ > + unsigned long next; > + p4d_t *p4dp = p4d_offset(pgdp, addr); > + > + do { > + next = p4d_addr_end(addr, end); > + > + if (__p4d_none(false, p4dp_get(p4dp))) { > + > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > + > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > +#ifndef __PAGETABLE_PUD_FOLDED > + pud_init(__va(pud_phys)); > +#endif > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > + continue; > + } else { > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > + sizeof(kasan_early_shadow_pud)); > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > + } > + } > + kasan_shallow_populate_pud(p4dp, addr, next, node); > + } while (p4dp++, addr = next, addr != end); > +} > + > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > + unsigned long end, int node) > +{ > + unsigned long next; > + pgd_t *pgdp; > + > + pgdp = pgd_offset_k(addr); > + > + do { > + next = pgd_addr_end(addr, end); > + > + if (__pgd_none(false, pgdp_get(pgdp))) { > + > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > + > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > + pgd_init(__va(p4d_phys)); > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > + continue; > + } else { > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > + sizeof(kasan_early_shadow_p4d)); > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > + } > + } > + > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > + } while (pgdp++, addr = next, addr != end); > +} > + > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > +{ > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > + > + kasan_shallow_populate_pgd(vaddr, vend, node); > +} > + > +#ifdef CONFIG_KASAN_VMALLOC > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > +{ > + unsigned long shadow_start, shadow_end; > + > + if (!is_vmalloc_or_module_addr(start)) > + return; > + > + shadow_start = (unsigned long)mem_to_shadow(start); > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > + shadow_end = (unsigned long)mem_to_shadow(start + size); > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > + > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > +} > +#endif > + > asmlinkage void __init kasan_early_init(void) > { > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > @@ -297,9 +451,6 @@ void __init kasan_init(void) > /* Maps everything to a single page of zeroes */ > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > - mem_to_shadow((void *)KFENCE_AREA_END)); > - > /* Populate the linear mapping */ > for_each_mem_range(i, &pa_start, &pa_end) { > void *start = (void *)phys_to_virt(pa_start); > @@ -309,9 +460,9 @@ void __init kasan_init(void) > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > } > > - /* Populate modules mapping */ > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > + > /* > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > * should make sure that it maps the zero page read-only. > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-14 4:43 ` Huacai Chen @ 2026-10-16 15:04 ` haoran.jiang 2026-10-17 8:24 ` Huacai Chen 0 siblings, 1 reply; 8+ messages in thread From: haoran.jiang @ 2026-10-16 15:04 UTC (permalink / raw) To: Huacai Chen; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang 2026年10月14日 12:43, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > Hi, Haoran, > > On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > > > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > > > For the page table of the VMALLOC shadow region, > > only a partial page table needs to be established, > > allowing the complete page table entries to be > > populated on demand during use. > > > > --- > > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > > page tables are established for the modules and vmalloc regions, > > but the page tables are not fully populated; the complete page > > tables are established automatically when vmalloc is used. > > > Why also change modules together with vmalloc regions? Because they > can both alloc/free dynamically, and the current approch can only > handle "one time" for modules? > The shadow memory for modules regions is the same as that of vmalloc regions, full page tables can be built on demand during usage. Memory is allocated when loading modules, and the shadow memory of the allocated memory establishes complete page tables via `kasan_populate_vmalloc_do` (load_module->execmem_alloc->kasan_populate_vmalloc_do), similar to riscv and arm64. > > > > If the address is pgd-aligned, the page table is established > > down to the pgd level; if it is pud-aligned, it is established > > down to the pud level, and so on. While establishing the > > /pgd/pud/pmd levels, the page table entries must be initialized > > to none. For example, a pmd entry of none is an invalid_pte_table. > > Only in this way can the correct page tables be established at > > use time. At the same time, boundary issues must be considered > > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > > and mem_to_shadow(vmemmap) are in the same pmd, and the region > > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. > > > kasan_pgd_populate(early) populate the page table to early shadow > page, kasan_pgd_populate(!early) populate the page table to normal > shadow page, and kasan_shallow_populate() populate the page table to > invalid_{pud,pmd,pte}_table? > > It seems you are do similar work as RISCV, and RISCV use three APIs > for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch > use the same API for the 1st and 2nd purpose, can we add a "shallow" > parameter to kasan_pgd_populate() so we can also handle the 3th > purpose? > Yes. `kasan_shallow_populate()` builds incomplete shadow page tables for vmalloc and modules regions and needs to handle boundaries. As an example: `mem_to_shadow(VMALLOC_END)` and `mem_to_shadow(vmemmap)` share one PTE table. The PTE entry for `mem_to_shadow(VMALLOC_END)` must be none, whereas the PTE entry for `mem_to_shadow(vmemmap)` points to `early_shadow_page`. The final page table layout is shown below pte +------ +------------------+ |early_shadow_page | pmd +------------------+ +----------------+ |early_shadow_page | |early_shadow_pte| +------------------+ +----------------+ | global | | ptep |-----------+------------------+ +----------------+ | ... | pte +----------------+ +--------------+ | ... | | global | pgd +----------------+ +--------------+ +------------------+ | ptep |---------------| global | | pmdp +----------+----------------+ +--------------+ +------------------+ pmd | pmdp |----------+--------------+ +------------------+ |invalid_pte | | | +--------------+ | ... | |invalid_pte | | | +--------------+ +------------------+ | ... | +------------------+ pmd | ... | +------------+ +------------------+ | invalid_pte| | ... | +------------+ +------------------+ | invalid_pte| | pmdp |---------- +------------+ +------------------+ | ... | +------------------+ `kasan_pgd_populate()` can handle the third case by adding a parameter. I will implement this in the next version. > > > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > > Other architectures such as arm64/riscv need kasan_populate_early_shadow > > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > > page again. On LoongArch, however, the entire shadow space is first mapped to > > page 0 via kasan_pgd_populate, and then page tables are established for the > > required regions, which is simpler and clearer. > > > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > > because kasan_populate_early_vm_area_shadow requires them. > > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > > page table mappings for the early vmalloc regions before vmalloc_init. > > At this point, complete page tables need to be established, but > > kasan_populate_early_vm_area_shadow is called after step 1, at which time > > > I'm a little confusing here. What is "step 1" here? Step 1 refers to `kasan_shallow_populate()`. The page tables created after `kasan_shallow_populate()` are shown in the figure above. We need to check for empty entries using /pgd_none/p4d_none/pmd_none/pone_pte(). Only then can `kasan_map_populate()` build the full page tables. `kasan_populate_early_vm_area_shadow()` runs after `kasan_shallow_populate()` but before `vmalloc_init()`. At this stage, page tables for vmalloc regions cannot be automatically populated on demand, so we need to build full page tables here. > > > > > only incomplete page tables have been established for the vmalloc region. > > The condition for a page table entry to be none requires the > > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > > whether an entry is none can the complete page tables be established properly. > > > > Test: > > > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > > > CONFIG_KUNIT=y > > > > CONFIG_KASAN_KUNIT_TEST=m > > > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > > > before this patch: > > > > ... > > > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > > > ... > > > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > > [ 504.016171] ok 1 kasan > > > > after this patch: > > > > ... > > > > [ 531.630760] ok 68 vmalloc_oob > > > > ... > > > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > > [ 532.891219] ok 1 kasan > > > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > > > --- > > arch/loongarch/Kconfig | 2 + > > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > > 2 files changed, 164 insertions(+), 11 deletions(-) > > > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > > index 2067d1f2ad7a..c95c6fd8a138 100644 > > --- a/arch/loongarch/Kconfig > > +++ b/arch/loongarch/Kconfig > > @@ -119,6 +119,7 @@ config LOONGARCH > > select HAVE_ARCH_JUMP_LABEL > > select HAVE_ARCH_JUMP_LABEL_RELATIVE > > select HAVE_ARCH_KASAN if 64BIT > > + select HAVE_ARCH_KASAN_VMALLOC > > > I think this should be limited by 64BIT. > > Huacai I will fix this in the next revision. > > > > > select HAVE_ARCH_KFENCE if 64BIT > > select HAVE_ARCH_KGDB if PERF_EVENTS > > select HAVE_ARCH_KSTACK_ERASE > > @@ -187,6 +188,7 @@ config LOONGARCH > > select HOTPLUG_SMT if HOTPLUG_CPU > > select IRQ_FORCED_THREADING > > select IRQ_LOONGARCH_CPU > > + select KASAN_VMALLOC if KASAN > > select LOCK_MM_AND_FIND_VMA > > select MMU_GATHER_MERGE_VMAS if MMU > > select MMU_GATHER_RCU_TABLE_FREE > > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > > index 92ca0ba86406..962c37316200 100644 > > --- a/arch/loongarch/mm/kasan_init.c > > +++ b/arch/loongarch/mm/kasan_init.c > > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > > #define __pgd_none(early, pgd) (0) > > #else > > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > > #endif > > > > #ifdef __PAGETABLE_PUD_FOLDED > > #define __p4d_none(early, p4d) (0) > > #else > > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > > #endif > > > > #ifdef __PAGETABLE_PMD_FOLDED > > #define __pud_none(early, pud) (0) > > #else > > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > > #endif > > > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > > > static void *mem_to_shadow(const void *addr) > > { > > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > > } > > > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > > + unsigned long end, int node) > > +{ > > + unsigned long next; > > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > > + > > + do { > > + next = addr + PAGE_SIZE; > > + if (__pte_none(false, ptep_get(ptep))) > > + pte_clear(&init_mm, addr, ptep); > > + } while (ptep++, addr = next, addr != end); > > +} > > + > > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > > + unsigned long end, int node) > > +{ > > + unsigned long next; > > + pmd_t *pmdp = pmd_offset(pudp, addr); > > + > > + do { > > + next = pmd_addr_end(addr, end); > > + > > + if (__pmd_none(false, pmdp_get(pmdp))) { > > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > > + > > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > > + kernel_pte_init(__va(pte_phys)); > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > + continue; > > + } else { > > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > > + sizeof(kasan_early_shadow_pte)); > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > + } > > + } > > + kasan_shallow_populate_pte(pmdp, addr, next, node); > > + } while (pmdp++, addr = next, addr != end); > > +} > > + > > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > > + unsigned long end, int node) > > +{ > > + unsigned long next; > > + pud_t *pudp = pud_offset(p4dp, addr); > > + > > + do { > > + next = pud_addr_end(addr, end); > > + > > + if (__pud_none(false, pudp_get(pudp))) { > > + > > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > > + > > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > > +#ifndef __PAGETABLE_PMD_FOLDED > > + pmd_init(__va(pmd_phys)); > > +#endif > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > + continue; > > + } else { > > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > > + sizeof(kasan_early_shadow_pmd)); > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > + > > + } > > + } > > + > > + kasan_shallow_populate_pmd(pudp, addr, next, node); > > + } while (pudp++, addr = next, addr != end); > > +} > > + > > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > > + unsigned long end, int node) > > +{ > > + unsigned long next; > > + p4d_t *p4dp = p4d_offset(pgdp, addr); > > + > > + do { > > + next = p4d_addr_end(addr, end); > > + > > + if (__p4d_none(false, p4dp_get(p4dp))) { > > + > > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > > + > > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > > +#ifndef __PAGETABLE_PUD_FOLDED > > + pud_init(__va(pud_phys)); > > +#endif > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > + continue; > > + } else { > > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > > + sizeof(kasan_early_shadow_pud)); > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > + } > > + } > > + kasan_shallow_populate_pud(p4dp, addr, next, node); > > + } while (p4dp++, addr = next, addr != end); > > +} > > + > > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > > + unsigned long end, int node) > > +{ > > + unsigned long next; > > + pgd_t *pgdp; > > + > > + pgdp = pgd_offset_k(addr); > > + > > + do { > > + next = pgd_addr_end(addr, end); > > + > > + if (__pgd_none(false, pgdp_get(pgdp))) { > > + > > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > > + > > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > > + pgd_init(__va(p4d_phys)); > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > + continue; > > + } else { > > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > > + sizeof(kasan_early_shadow_p4d)); > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > + } > > + } > > + > > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > > + } while (pgdp++, addr = next, addr != end); > > +} > > + > > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > > +{ > > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > > + > > + kasan_shallow_populate_pgd(vaddr, vend, node); > > +} > > + > > +#ifdef CONFIG_KASAN_VMALLOC > > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > > +{ > > + unsigned long shadow_start, shadow_end; > > + > > + if (!is_vmalloc_or_module_addr(start)) > > + return; > > + > > + shadow_start = (unsigned long)mem_to_shadow(start); > > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > > + shadow_end = (unsigned long)mem_to_shadow(start + size); > > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > > + > > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > > +} > > +#endif > > + > > asmlinkage void __init kasan_early_init(void) > > { > > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > > @@ -297,9 +451,6 @@ void __init kasan_init(void) > > /* Maps everything to a single page of zeroes */ > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > - mem_to_shadow((void *)KFENCE_AREA_END)); > > - > > /* Populate the linear mapping */ > > for_each_mem_range(i, &pa_start, &pa_end) { > > void *start = (void *)phys_to_virt(pa_start); > > @@ -309,9 +460,9 @@ void __init kasan_init(void) > > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > > } > > > > - /* Populate modules mapping */ > > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > > + > > /* > > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > > * should make sure that it maps the zero page read-only. > > -- > > 2.43.0 > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-16 15:04 ` haoran.jiang @ 2026-10-17 8:24 ` Huacai Chen 2026-10-20 2:37 ` haoran.jiang 0 siblings, 1 reply; 8+ messages in thread From: Huacai Chen @ 2026-10-17 8:24 UTC (permalink / raw) To: haoran.jiang; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang On Fri, Oct 16, 2026 at 11:05 PM <haoran.jiang@linux.dev> wrote: > > 2026年10月14日 12:43, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > Hi, Haoran, > > > > On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > For the page table of the VMALLOC shadow region, > > > only a partial page table needs to be established, > > > allowing the complete page table entries to be > > > populated on demand during use. > > > > > > --- > > > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > > > page tables are established for the modules and vmalloc regions, > > > but the page tables are not fully populated; the complete page > > > tables are established automatically when vmalloc is used. > > > > > Why also change modules together with vmalloc regions? Because they > > can both alloc/free dynamically, and the current approch can only > > handle "one time" for modules? > > > > The shadow memory for modules regions is the same as that of vmalloc regions, > full page tables can be built on demand during usage. Memory is allocated > when loading modules, and the shadow memory of the allocated memory establishes > complete page tables via `kasan_populate_vmalloc_do` > (load_module->execmem_alloc->kasan_populate_vmalloc_do), similar to riscv and arm64. The current code can also handle modules, so I want to know the difference. I guess the current code can only handle a module loading once, but this patch makes it possible to load/unload modules repeatedly. But I'm not sure. Anyway, I don't want something to become worse. > > > > > > > If the address is pgd-aligned, the page table is established > > > down to the pgd level; if it is pud-aligned, it is established > > > down to the pud level, and so on. While establishing the > > > /pgd/pud/pmd levels, the page table entries must be initialized > > > to none. For example, a pmd entry of none is an invalid_pte_table. > > > Only in this way can the correct page tables be established at > > > use time. At the same time, boundary issues must be considered > > > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > > > and mem_to_shadow(vmemmap) are in the same pmd, and the region > > > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > > > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > > > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > > > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. > > > > > kasan_pgd_populate(early) populate the page table to early shadow > > page, kasan_pgd_populate(!early) populate the page table to normal > > shadow page, and kasan_shallow_populate() populate the page table to > > invalid_{pud,pmd,pte}_table? > > > > It seems you are do similar work as RISCV, and RISCV use three APIs > > for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch > > use the same API for the 1st and 2nd purpose, can we add a "shallow" > > parameter to kasan_pgd_populate() so we can also handle the 3th > > purpose? > > > > Yes. `kasan_shallow_populate()` builds incomplete shadow page tables for vmalloc and modules > regions and needs to handle boundaries. As an example: > `mem_to_shadow(VMALLOC_END)` and `mem_to_shadow(vmemmap)` share one PTE table. > The PTE entry for `mem_to_shadow(VMALLOC_END)` must be none, whereas the PTE entry > for `mem_to_shadow(vmemmap)` points to `early_shadow_page`. The final page table > layout is shown below > > > > pte +------ > +------------------+ > |early_shadow_page | > pmd +------------------+ > +----------------+ |early_shadow_page | > |early_shadow_pte| +------------------+ > +----------------+ | global | > | ptep |-----------+------------------+ > +----------------+ > | ... | pte > +----------------+ +--------------+ > | ... | | global | > pgd +----------------+ +--------------+ > +------------------+ | ptep |---------------| global | > | pmdp +----------+----------------+ +--------------+ > +------------------+ pmd > | pmdp |----------+--------------+ > +------------------+ |invalid_pte | > | | +--------------+ > | ... | |invalid_pte | > | | +--------------+ > +------------------+ > | ... | > +------------------+ pmd > | ... | +------------+ > +------------------+ | invalid_pte| > | ... | +------------+ > +------------------+ | invalid_pte| > | pmdp |---------- +------------+ > +------------------+ > | ... | > +------------------+ > > > `kasan_pgd_populate()` can handle the third case by adding a parameter. I will implement this in the next version. > > > > > > > > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > > > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > > > Other architectures such as arm64/riscv need kasan_populate_early_shadow > > > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > > > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > > > page again. On LoongArch, however, the entire shadow space is first mapped to > > > page 0 via kasan_pgd_populate, and then page tables are established for the > > > required regions, which is simpler and clearer. > > > > > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > > > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > > > because kasan_populate_early_vm_area_shadow requires them. > > > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > > > page table mappings for the early vmalloc regions before vmalloc_init. > > > At this point, complete page tables need to be established, but > > > kasan_populate_early_vm_area_shadow is called after step 1, at which time > > > > > I'm a little confusing here. What is "step 1" here? > > Step 1 refers to `kasan_shallow_populate()`. The page tables created after > `kasan_shallow_populate()` are shown in the figure above. We need to check > for empty entries using /pgd_none/p4d_none/pmd_none/pone_pte(). Only then > can `kasan_map_populate()` build the full page tables. > `kasan_populate_early_vm_area_shadow()` runs after > `kasan_shallow_populate()` but before `vmalloc_init()`. > At this stage, page tables for vmalloc regions cannot be automatically > populated on demand, so we need to build full page tables here. Then it is better to use `kasan_shallow_populate()` rather than `step 1`. Huacai > > > > > > > > > > only incomplete page tables have been established for the vmalloc region. > > > The condition for a page table entry to be none requires the > > > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > > > whether an entry is none can the complete page tables be established properly. > > > > > > Test: > > > > > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > > > > > CONFIG_KUNIT=y > > > > > > CONFIG_KASAN_KUNIT_TEST=m > > > > > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > > > > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > > > > > before this patch: > > > > > > ... > > > > > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > > > > > ... > > > > > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > > > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > > > [ 504.016171] ok 1 kasan > > > > > > after this patch: > > > > > > ... > > > > > > [ 531.630760] ok 68 vmalloc_oob > > > > > > ... > > > > > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > > > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > > > [ 532.891219] ok 1 kasan > > > > > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > --- > > > arch/loongarch/Kconfig | 2 + > > > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > > > 2 files changed, 164 insertions(+), 11 deletions(-) > > > > > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > > > index 2067d1f2ad7a..c95c6fd8a138 100644 > > > --- a/arch/loongarch/Kconfig > > > +++ b/arch/loongarch/Kconfig > > > @@ -119,6 +119,7 @@ config LOONGARCH > > > select HAVE_ARCH_JUMP_LABEL > > > select HAVE_ARCH_JUMP_LABEL_RELATIVE > > > select HAVE_ARCH_KASAN if 64BIT > > > + select HAVE_ARCH_KASAN_VMALLOC > > > > > I think this should be limited by 64BIT. > > > > Huacai > > I will fix this in the next revision. > > > > > > > > > select HAVE_ARCH_KFENCE if 64BIT > > > select HAVE_ARCH_KGDB if PERF_EVENTS > > > select HAVE_ARCH_KSTACK_ERASE > > > @@ -187,6 +188,7 @@ config LOONGARCH > > > select HOTPLUG_SMT if HOTPLUG_CPU > > > select IRQ_FORCED_THREADING > > > select IRQ_LOONGARCH_CPU > > > + select KASAN_VMALLOC if KASAN > > > select LOCK_MM_AND_FIND_VMA > > > select MMU_GATHER_MERGE_VMAS if MMU > > > select MMU_GATHER_RCU_TABLE_FREE > > > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > > > index 92ca0ba86406..962c37316200 100644 > > > --- a/arch/loongarch/mm/kasan_init.c > > > +++ b/arch/loongarch/mm/kasan_init.c > > > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > > > #define __pgd_none(early, pgd) (0) > > > #else > > > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > > > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > > > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > > > #endif > > > > > > #ifdef __PAGETABLE_PUD_FOLDED > > > #define __p4d_none(early, p4d) (0) > > > #else > > > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > > > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > > > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > > > #endif > > > > > > #ifdef __PAGETABLE_PMD_FOLDED > > > #define __pud_none(early, pud) (0) > > > #else > > > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > > > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > > > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > > > #endif > > > > > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > > > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > > > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > > > > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > > > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > > > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > > > > > static void *mem_to_shadow(const void *addr) > > > { > > > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > > > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > > > } > > > > > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > > > + > > > + do { > > > + next = addr + PAGE_SIZE; > > > + if (__pte_none(false, ptep_get(ptep))) > > > + pte_clear(&init_mm, addr, ptep); > > > + } while (ptep++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pmd_t *pmdp = pmd_offset(pudp, addr); > > > + > > > + do { > > > + next = pmd_addr_end(addr, end); > > > + > > > + if (__pmd_none(false, pmdp_get(pmdp))) { > > > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > > > + kernel_pte_init(__va(pte_phys)); > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > > > + sizeof(kasan_early_shadow_pte)); > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > + } > > > + } > > > + kasan_shallow_populate_pte(pmdp, addr, next, node); > > > + } while (pmdp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pud_t *pudp = pud_offset(p4dp, addr); > > > + > > > + do { > > > + next = pud_addr_end(addr, end); > > > + > > > + if (__pud_none(false, pudp_get(pudp))) { > > > + > > > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > > > +#ifndef __PAGETABLE_PMD_FOLDED > > > + pmd_init(__va(pmd_phys)); > > > +#endif > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > > > + sizeof(kasan_early_shadow_pmd)); > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > + > > > + } > > > + } > > > + > > > + kasan_shallow_populate_pmd(pudp, addr, next, node); > > > + } while (pudp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + p4d_t *p4dp = p4d_offset(pgdp, addr); > > > + > > > + do { > > > + next = p4d_addr_end(addr, end); > > > + > > > + if (__p4d_none(false, p4dp_get(p4dp))) { > > > + > > > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > > > +#ifndef __PAGETABLE_PUD_FOLDED > > > + pud_init(__va(pud_phys)); > > > +#endif > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > > > + sizeof(kasan_early_shadow_pud)); > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > + } > > > + } > > > + kasan_shallow_populate_pud(p4dp, addr, next, node); > > > + } while (p4dp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pgd_t *pgdp; > > > + > > > + pgdp = pgd_offset_k(addr); > > > + > > > + do { > > > + next = pgd_addr_end(addr, end); > > > + > > > + if (__pgd_none(false, pgdp_get(pgdp))) { > > > + > > > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > > > + pgd_init(__va(p4d_phys)); > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > > > + sizeof(kasan_early_shadow_p4d)); > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > + } > > > + } > > > + > > > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > > > + } while (pgdp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > > > +{ > > > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > > > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > > > + > > > + kasan_shallow_populate_pgd(vaddr, vend, node); > > > +} > > > + > > > +#ifdef CONFIG_KASAN_VMALLOC > > > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > > > +{ > > > + unsigned long shadow_start, shadow_end; > > > + > > > + if (!is_vmalloc_or_module_addr(start)) > > > + return; > > > + > > > + shadow_start = (unsigned long)mem_to_shadow(start); > > > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > > > + shadow_end = (unsigned long)mem_to_shadow(start + size); > > > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > > > + > > > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > > > +} > > > +#endif > > > + > > > asmlinkage void __init kasan_early_init(void) > > > { > > > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > > > @@ -297,9 +451,6 @@ void __init kasan_init(void) > > > /* Maps everything to a single page of zeroes */ > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > - mem_to_shadow((void *)KFENCE_AREA_END)); > > > - > > > /* Populate the linear mapping */ > > > for_each_mem_range(i, &pa_start, &pa_end) { > > > void *start = (void *)phys_to_virt(pa_start); > > > @@ -309,9 +460,9 @@ void __init kasan_init(void) > > > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > > > } > > > > > > - /* Populate modules mapping */ > > > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > > > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > > > + > > > /* > > > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > > > * should make sure that it maps the zero page read-only. > > > -- > > > 2.43.0 > > > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-17 8:24 ` Huacai Chen @ 2026-10-20 2:37 ` haoran.jiang 2026-10-20 7:47 ` Huacai Chen 0 siblings, 1 reply; 8+ messages in thread From: haoran.jiang @ 2026-10-20 2:37 UTC (permalink / raw) To: Huacai Chen; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang 2026年10月17日 16:24, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > On Fri, Oct 16, 2026 at 11:05 PM <haoran.jiang@linux.dev> wrote: > > > > > 2026年10月14日 12:43, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > Hi, Haoran, > > > > On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > For the page table of the VMALLOC shadow region, > > > only a partial page table needs to be established, > > > allowing the complete page table entries to be > > > populated on demand during use. > > > > > > --- > > > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > > > page tables are established for the modules and vmalloc regions, > > > but the page tables are not fully populated; the complete page > > > tables are established automatically when vmalloc is used. > > > > > Why also change modules together with vmalloc regions? Because they > > can both alloc/free dynamically, and the current approch can only > > handle "one time" for modules? > > > > The shadow memory for modules regions is the same as that of vmalloc regions, > > full page tables can be built on demand during usage. Memory is allocated > > when loading modules, and the shadow memory of the allocated memory establishes > > complete page tables via `kasan_populate_vmalloc_do` > > (load_module->execmem_alloc->kasan_populate_vmalloc_do), similar to riscv and arm64. > > > The current code can also handle modules, so I want to know the > difference. I guess the current code can only handle a module loading > once, but this patch makes it possible to load/unload modules > repeatedly. But I'm not sure. Anyway, I don't want something to become > worse. > 1,With the current approach, the shadow page tables for the modules region are established via `kasan_map_populate` and `KASAN_VMALLOC=n`. A test module performs an out-of-bounds write to a global array. After repeated `insmod`/`rmmod` cycles, the test module reports false positives. This is because the current flow does not properly clean up the shadow data. Taking x86 as an example, since x86 can be configured with KASAN_VMALLOC=n, when KASAN_VMALLOC=n, the correct flow is as follows: execmem_arch_setup -> flags = EXECMEM_KASAN_SHADOW, the flag is set to EXECMEM_KASAN_SHADOW, and the subsequent flow should select the correct path based on the EXECMEM_KASAN_SHADOW flag. insmod -> execmem_vmalloc -> (kasan && (kasan_alloc_module_shadow(p, size, GFP_KERNEL) -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow -> vfree(kasan_mem_to_shadow(vm->addr)); This flow it establishes the shadow page tables, then poisons them, and cleans up the shadow data when the module is unloaded. However, the current situation on LoongArch is as follows: execmem_arch_setup -> does not set EXECMEM_KASAN_SHADOW insmod -> execmem_vmalloc -> does not call kasan_alloc_module_shadow -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow -> does not call vfree(kasan_mem_to_shadow(vm->addr)); LoongArch does not set EXECMEM_KASAN_SHADOW during initialization, which causes the shadow data not to be properly cleaned up at unload time, potentially leading to false positives the next time a module is loaded. On RISC-V/arm64, KASAN_VMALLOC is enabled by default, and the shadow page tables for the module region are established and torn down via kasan_populate_vmalloc_do/kasan_release_vmalloc. This is exactly the approach that kasan_shallow_populate aims to implement. 2,kasan_shallow_populate requires fewer page tables than kasan_map_populate, and it can also save memory during initialization. > > > > > > > > If the address is pgd-aligned, the page table is established > > > down to the pgd level; if it is pud-aligned, it is established > > > down to the pud level, and so on. While establishing the > > > /pgd/pud/pmd levels, the page table entries must be initialized > > > to none. For example, a pmd entry of none is an invalid_pte_table. > > > Only in this way can the correct page tables be established at > > > use time. At the same time, boundary issues must be considered > > > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > > > and mem_to_shadow(vmemmap) are in the same pmd, and the region > > > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > > > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > > > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > > > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. > > > > > kasan_pgd_populate(early) populate the page table to early shadow > > page, kasan_pgd_populate(!early) populate the page table to normal > > shadow page, and kasan_shallow_populate() populate the page table to > > invalid_{pud,pmd,pte}_table? > > > > It seems you are do similar work as RISCV, and RISCV use three APIs > > for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch > > use the same API for the 1st and 2nd purpose, can we add a "shallow" > > parameter to kasan_pgd_populate() so we can also handle the 3th > > purpose? > > > > Yes. `kasan_shallow_populate()` builds incomplete shadow page tables for vmalloc and modules > > regions and needs to handle boundaries. As an example: > > `mem_to_shadow(VMALLOC_END)` and `mem_to_shadow(vmemmap)` share one PTE table. > > The PTE entry for `mem_to_shadow(VMALLOC_END)` must be none, whereas the PTE entry > > for `mem_to_shadow(vmemmap)` points to `early_shadow_page`. The final page table > > layout is shown below > > > > pte +------ > > +------------------+ > > |early_shadow_page | > > pmd +------------------+ > > +----------------+ |early_shadow_page | > > |early_shadow_pte| +------------------+ > > +----------------+ | global | > > | ptep |-----------+------------------+ > > +----------------+ > > | ... | pte > > +----------------+ +--------------+ > > | ... | | global | > > pgd +----------------+ +--------------+ > > +------------------+ | ptep |---------------| global | > > | pmdp +----------+----------------+ +--------------+ > > +------------------+ pmd > > | pmdp |----------+--------------+ > > +------------------+ |invalid_pte | > > | | +--------------+ > > | ... | |invalid_pte | > > | | +--------------+ > > +------------------+ > > | ... | > > +------------------+ pmd > > | ... | +------------+ > > +------------------+ | invalid_pte| > > | ... | +------------+ > > +------------------+ | invalid_pte| > > | pmdp |---------- +------------+ > > +------------------+ > > | ... | > > +------------------+ > > > > `kasan_pgd_populate()` can handle the third case by adding a parameter. I will implement this in the next version. > > > > > > > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > > > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > > > Other architectures such as arm64/riscv need kasan_populate_early_shadow > > > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > > > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > > > page again. On LoongArch, however, the entire shadow space is first mapped to > > > page 0 via kasan_pgd_populate, and then page tables are established for the > > > required regions, which is simpler and clearer. > > > > > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > > > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > > > because kasan_populate_early_vm_area_shadow requires them. > > > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > > > page table mappings for the early vmalloc regions before vmalloc_init. > > > At this point, complete page tables need to be established, but > > > kasan_populate_early_vm_area_shadow is called after step 1, at which time > > > > > I'm a little confusing here. What is "step 1" here? > > > > Step 1 refers to `kasan_shallow_populate()`. The page tables created after > > `kasan_shallow_populate()` are shown in the figure above. We need to check > > for empty entries using /pgd_none/p4d_none/pmd_none/pone_pte(). Only then > > can `kasan_map_populate()` build the full page tables. > > `kasan_populate_early_vm_area_shadow()` runs after > > `kasan_shallow_populate()` but before `vmalloc_init()`. > > At this stage, page tables for vmalloc regions cannot be automatically > > populated on demand, so we need to build full page tables here. > > > Then it is better to use `kasan_shallow_populate()` rather than `step 1`. > > Huacai > > > > > > > > > only incomplete page tables have been established for the vmalloc region. > > > The condition for a page table entry to be none requires the > > > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > > > whether an entry is none can the complete page tables be established properly. > > > > > > Test: > > > > > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > > > > > CONFIG_KUNIT=y > > > > > > CONFIG_KASAN_KUNIT_TEST=m > > > > > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > > > > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > > > > > before this patch: > > > > > > ... > > > > > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > > > > > ... > > > > > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > > > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > > > [ 504.016171] ok 1 kasan > > > > > > after this patch: > > > > > > ... > > > > > > [ 531.630760] ok 68 vmalloc_oob > > > > > > ... > > > > > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > > > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > > > [ 532.891219] ok 1 kasan > > > > > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > --- > > > arch/loongarch/Kconfig | 2 + > > > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > > > 2 files changed, 164 insertions(+), 11 deletions(-) > > > > > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > > > index 2067d1f2ad7a..c95c6fd8a138 100644 > > > --- a/arch/loongarch/Kconfig > > > +++ b/arch/loongarch/Kconfig > > > @@ -119,6 +119,7 @@ config LOONGARCH > > > select HAVE_ARCH_JUMP_LABEL > > > select HAVE_ARCH_JUMP_LABEL_RELATIVE > > > select HAVE_ARCH_KASAN if 64BIT > > > + select HAVE_ARCH_KASAN_VMALLOC > > > > > I think this should be limited by 64BIT. > > > > Huacai > > > > I will fix this in the next revision. > > > > > > > > select HAVE_ARCH_KFENCE if 64BIT > > > select HAVE_ARCH_KGDB if PERF_EVENTS > > > select HAVE_ARCH_KSTACK_ERASE > > > @@ -187,6 +188,7 @@ config LOONGARCH > > > select HOTPLUG_SMT if HOTPLUG_CPU > > > select IRQ_FORCED_THREADING > > > select IRQ_LOONGARCH_CPU > > > + select KASAN_VMALLOC if KASAN > > > select LOCK_MM_AND_FIND_VMA > > > select MMU_GATHER_MERGE_VMAS if MMU > > > select MMU_GATHER_RCU_TABLE_FREE > > > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > > > index 92ca0ba86406..962c37316200 100644 > > > --- a/arch/loongarch/mm/kasan_init.c > > > +++ b/arch/loongarch/mm/kasan_init.c > > > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > > > #define __pgd_none(early, pgd) (0) > > > #else > > > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > > > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > > > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > > > #endif > > > > > > #ifdef __PAGETABLE_PUD_FOLDED > > > #define __p4d_none(early, p4d) (0) > > > #else > > > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > > > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > > > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > > > #endif > > > > > > #ifdef __PAGETABLE_PMD_FOLDED > > > #define __pud_none(early, pud) (0) > > > #else > > > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > > > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > > > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > > > #endif > > > > > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > > > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > > > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > > > > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > > > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > > > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > > > > > static void *mem_to_shadow(const void *addr) > > > { > > > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > > > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > > > } > > > > > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > > > + > > > + do { > > > + next = addr + PAGE_SIZE; > > > + if (__pte_none(false, ptep_get(ptep))) > > > + pte_clear(&init_mm, addr, ptep); > > > + } while (ptep++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pmd_t *pmdp = pmd_offset(pudp, addr); > > > + > > > + do { > > > + next = pmd_addr_end(addr, end); > > > + > > > + if (__pmd_none(false, pmdp_get(pmdp))) { > > > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > > > + kernel_pte_init(__va(pte_phys)); > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > > > + sizeof(kasan_early_shadow_pte)); > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > + } > > > + } > > > + kasan_shallow_populate_pte(pmdp, addr, next, node); > > > + } while (pmdp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pud_t *pudp = pud_offset(p4dp, addr); > > > + > > > + do { > > > + next = pud_addr_end(addr, end); > > > + > > > + if (__pud_none(false, pudp_get(pudp))) { > > > + > > > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > > > +#ifndef __PAGETABLE_PMD_FOLDED > > > + pmd_init(__va(pmd_phys)); > > > +#endif > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > > > + sizeof(kasan_early_shadow_pmd)); > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > + > > > + } > > > + } > > > + > > > + kasan_shallow_populate_pmd(pudp, addr, next, node); > > > + } while (pudp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + p4d_t *p4dp = p4d_offset(pgdp, addr); > > > + > > > + do { > > > + next = p4d_addr_end(addr, end); > > > + > > > + if (__p4d_none(false, p4dp_get(p4dp))) { > > > + > > > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > > > +#ifndef __PAGETABLE_PUD_FOLDED > > > + pud_init(__va(pud_phys)); > > > +#endif > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > > > + sizeof(kasan_early_shadow_pud)); > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > + } > > > + } > > > + kasan_shallow_populate_pud(p4dp, addr, next, node); > > > + } while (p4dp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > > > + unsigned long end, int node) > > > +{ > > > + unsigned long next; > > > + pgd_t *pgdp; > > > + > > > + pgdp = pgd_offset_k(addr); > > > + > > > + do { > > > + next = pgd_addr_end(addr, end); > > > + > > > + if (__pgd_none(false, pgdp_get(pgdp))) { > > > + > > > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > > > + > > > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > > > + pgd_init(__va(p4d_phys)); > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > + continue; > > > + } else { > > > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > > > + sizeof(kasan_early_shadow_p4d)); > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > + } > > > + } > > > + > > > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > > > + } while (pgdp++, addr = next, addr != end); > > > +} > > > + > > > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > > > +{ > > > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > > > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > > > + > > > + kasan_shallow_populate_pgd(vaddr, vend, node); > > > +} > > > + > > > +#ifdef CONFIG_KASAN_VMALLOC > > > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > > > +{ > > > + unsigned long shadow_start, shadow_end; > > > + > > > + if (!is_vmalloc_or_module_addr(start)) > > > + return; > > > + > > > + shadow_start = (unsigned long)mem_to_shadow(start); > > > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > > > + shadow_end = (unsigned long)mem_to_shadow(start + size); > > > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > > > + > > > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > > > +} > > > +#endif > > > + > > > asmlinkage void __init kasan_early_init(void) > > > { > > > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > > > @@ -297,9 +451,6 @@ void __init kasan_init(void) > > > /* Maps everything to a single page of zeroes */ > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > - mem_to_shadow((void *)KFENCE_AREA_END)); > > > - > > > /* Populate the linear mapping */ > > > for_each_mem_range(i, &pa_start, &pa_end) { > > > void *start = (void *)phys_to_virt(pa_start); > > > @@ -309,9 +460,9 @@ void __init kasan_init(void) > > > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > > > } > > > > > > - /* Populate modules mapping */ > > > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > > > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > > > + > > > /* > > > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > > > * should make sure that it maps the zero page read-only. > > > -- > > > 2.43.0 > > > > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-20 2:37 ` haoran.jiang @ 2026-10-20 7:47 ` Huacai Chen 2026-10-20 9:40 ` haoran.jiang 0 siblings, 1 reply; 8+ messages in thread From: Huacai Chen @ 2026-10-20 7:47 UTC (permalink / raw) To: haoran.jiang; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang On Tue, Oct 20, 2026 at 10:37 AM <haoran.jiang@linux.dev> wrote: > > 2026年10月17日 16:24, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > On Fri, Oct 16, 2026 at 11:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > 2026年10月14日 12:43, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > Hi, Haoran, > > > > > > On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > > > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > > > For the page table of the VMALLOC shadow region, > > > > only a partial page table needs to be established, > > > > allowing the complete page table entries to be > > > > populated on demand during use. > > > > > > > > --- > > > > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > > > > page tables are established for the modules and vmalloc regions, > > > > but the page tables are not fully populated; the complete page > > > > tables are established automatically when vmalloc is used. > > > > > > > Why also change modules together with vmalloc regions? Because they > > > can both alloc/free dynamically, and the current approch can only > > > handle "one time" for modules? > > > > > > The shadow memory for modules regions is the same as that of vmalloc regions, > > > full page tables can be built on demand during usage. Memory is allocated > > > when loading modules, and the shadow memory of the allocated memory establishes > > > complete page tables via `kasan_populate_vmalloc_do` > > > (load_module->execmem_alloc->kasan_populate_vmalloc_do), similar to riscv and arm64. > > > > > The current code can also handle modules, so I want to know the > > difference. I guess the current code can only handle a module loading > > once, but this patch makes it possible to load/unload modules > > repeatedly. But I'm not sure. Anyway, I don't want something to become > > worse. > > > > 1,With the current approach, the shadow page tables for the modules > region are established via `kasan_map_populate` and `KASAN_VMALLOC=n`. > A test module performs an out-of-bounds write to a global array. After > repeated `insmod`/`rmmod` cycles, the test module reports false positives. > This is because the current flow does not properly clean up the shadow data. > > > > Taking x86 as an example, since x86 can be configured with KASAN_VMALLOC=n, > when KASAN_VMALLOC=n, the correct flow is as follows: x86 has some trouble implementing KASAN_VMALLOC? Or does x86's solution have its advantage? Huacai > > execmem_arch_setup -> flags = EXECMEM_KASAN_SHADOW, the flag is set to EXECMEM_KASAN_SHADOW, > and the subsequent flow should select the correct path based on the EXECMEM_KASAN_SHADOW flag. > > insmod -> execmem_vmalloc -> (kasan && (kasan_alloc_module_shadow(p, size, GFP_KERNEL) > -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow > -> vfree(kasan_mem_to_shadow(vm->addr)); > > This flow it establishes the shadow page tables, then poisons them, > and cleans up the shadow data when the module is unloaded. > > > > However, the current situation on LoongArch is as follows: > > execmem_arch_setup -> does not set EXECMEM_KASAN_SHADOW > > insmod -> execmem_vmalloc -> does not call kasan_alloc_module_shadow > -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow > -> does not call vfree(kasan_mem_to_shadow(vm->addr)); > > LoongArch does not set EXECMEM_KASAN_SHADOW during initialization, which > causes the shadow data not to be properly cleaned up at unload time, > potentially leading to false positives the next time a module is loaded. > > > > On RISC-V/arm64, KASAN_VMALLOC is enabled by default, and the shadow page > tables for the module region are established and torn down via > kasan_populate_vmalloc_do/kasan_release_vmalloc. This is exactly > the approach that kasan_shallow_populate aims to implement. > > > > 2,kasan_shallow_populate requires fewer page tables than kasan_map_populate, > and it can also save memory during initialization. > > > > > > > > > > > > > If the address is pgd-aligned, the page table is established > > > > down to the pgd level; if it is pud-aligned, it is established > > > > down to the pud level, and so on. While establishing the > > > > /pgd/pud/pmd levels, the page table entries must be initialized > > > > to none. For example, a pmd entry of none is an invalid_pte_table. > > > > Only in this way can the correct page tables be established at > > > > use time. At the same time, boundary issues must be considered > > > > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > > > > and mem_to_shadow(vmemmap) are in the same pmd, and the region > > > > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > > > > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > > > > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > > > > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. > > > > > > > kasan_pgd_populate(early) populate the page table to early shadow > > > page, kasan_pgd_populate(!early) populate the page table to normal > > > shadow page, and kasan_shallow_populate() populate the page table to > > > invalid_{pud,pmd,pte}_table? > > > > > > It seems you are do similar work as RISCV, and RISCV use three APIs > > > for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch > > > use the same API for the 1st and 2nd purpose, can we add a "shallow" > > > parameter to kasan_pgd_populate() so we can also handle the 3th > > > purpose? > > > > > > Yes. `kasan_shallow_populate()` builds incomplete shadow page tables for vmalloc and modules > > > regions and needs to handle boundaries. As an example: > > > `mem_to_shadow(VMALLOC_END)` and `mem_to_shadow(vmemmap)` share one PTE table. > > > The PTE entry for `mem_to_shadow(VMALLOC_END)` must be none, whereas the PTE entry > > > for `mem_to_shadow(vmemmap)` points to `early_shadow_page`. The final page table > > > layout is shown below > > > > > > pte +------ > > > +------------------+ > > > |early_shadow_page | > > > pmd +------------------+ > > > +----------------+ |early_shadow_page | > > > |early_shadow_pte| +------------------+ > > > +----------------+ | global | > > > | ptep |-----------+------------------+ > > > +----------------+ > > > | ... | pte > > > +----------------+ +--------------+ > > > | ... | | global | > > > pgd +----------------+ +--------------+ > > > +------------------+ | ptep |---------------| global | > > > | pmdp +----------+----------------+ +--------------+ > > > +------------------+ pmd > > > | pmdp |----------+--------------+ > > > +------------------+ |invalid_pte | > > > | | +--------------+ > > > | ... | |invalid_pte | > > > | | +--------------+ > > > +------------------+ > > > | ... | > > > +------------------+ pmd > > > | ... | +------------+ > > > +------------------+ | invalid_pte| > > > | ... | +------------+ > > > +------------------+ | invalid_pte| > > > | pmdp |---------- +------------+ > > > +------------------+ > > > | ... | > > > +------------------+ > > > > > > `kasan_pgd_populate()` can handle the third case by adding a parameter. I will implement this in the next version. > > > > > > > > > > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > > > > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > > > > Other architectures such as arm64/riscv need kasan_populate_early_shadow > > > > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > > > > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > > > > page again. On LoongArch, however, the entire shadow space is first mapped to > > > > page 0 via kasan_pgd_populate, and then page tables are established for the > > > > required regions, which is simpler and clearer. > > > > > > > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > > > > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > > > > because kasan_populate_early_vm_area_shadow requires them. > > > > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > > > > page table mappings for the early vmalloc regions before vmalloc_init. > > > > At this point, complete page tables need to be established, but > > > > kasan_populate_early_vm_area_shadow is called after step 1, at which time > > > > > > > I'm a little confusing here. What is "step 1" here? > > > > > > Step 1 refers to `kasan_shallow_populate()`. The page tables created after > > > `kasan_shallow_populate()` are shown in the figure above. We need to check > > > for empty entries using /pgd_none/p4d_none/pmd_none/pone_pte(). Only then > > > can `kasan_map_populate()` build the full page tables. > > > `kasan_populate_early_vm_area_shadow()` runs after > > > `kasan_shallow_populate()` but before `vmalloc_init()`. > > > At this stage, page tables for vmalloc regions cannot be automatically > > > populated on demand, so we need to build full page tables here. > > > > > Then it is better to use `kasan_shallow_populate()` rather than `step 1`. > > > > Huacai > > > > > > > > > > > > > only incomplete page tables have been established for the vmalloc region. > > > > The condition for a page table entry to be none requires the > > > > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > > > > whether an entry is none can the complete page tables be established properly. > > > > > > > > Test: > > > > > > > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > > > > > > > CONFIG_KUNIT=y > > > > > > > > CONFIG_KASAN_KUNIT_TEST=m > > > > > > > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > > > > > > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > > > > > > > before this patch: > > > > > > > > ... > > > > > > > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > > > > > > > ... > > > > > > > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > > > > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > > > > [ 504.016171] ok 1 kasan > > > > > > > > after this patch: > > > > > > > > ... > > > > > > > > [ 531.630760] ok 68 vmalloc_oob > > > > > > > > ... > > > > > > > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > > > > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > > > > [ 532.891219] ok 1 kasan > > > > > > > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > > > --- > > > > arch/loongarch/Kconfig | 2 + > > > > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > > > > 2 files changed, 164 insertions(+), 11 deletions(-) > > > > > > > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > > > > index 2067d1f2ad7a..c95c6fd8a138 100644 > > > > --- a/arch/loongarch/Kconfig > > > > +++ b/arch/loongarch/Kconfig > > > > @@ -119,6 +119,7 @@ config LOONGARCH > > > > select HAVE_ARCH_JUMP_LABEL > > > > select HAVE_ARCH_JUMP_LABEL_RELATIVE > > > > select HAVE_ARCH_KASAN if 64BIT > > > > + select HAVE_ARCH_KASAN_VMALLOC > > > > > > > I think this should be limited by 64BIT. > > > > > > Huacai > > > > > > I will fix this in the next revision. > > > > > > > > > > > select HAVE_ARCH_KFENCE if 64BIT > > > > select HAVE_ARCH_KGDB if PERF_EVENTS > > > > select HAVE_ARCH_KSTACK_ERASE > > > > @@ -187,6 +188,7 @@ config LOONGARCH > > > > select HOTPLUG_SMT if HOTPLUG_CPU > > > > select IRQ_FORCED_THREADING > > > > select IRQ_LOONGARCH_CPU > > > > + select KASAN_VMALLOC if KASAN > > > > select LOCK_MM_AND_FIND_VMA > > > > select MMU_GATHER_MERGE_VMAS if MMU > > > > select MMU_GATHER_RCU_TABLE_FREE > > > > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > > > > index 92ca0ba86406..962c37316200 100644 > > > > --- a/arch/loongarch/mm/kasan_init.c > > > > +++ b/arch/loongarch/mm/kasan_init.c > > > > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > > > > #define __pgd_none(early, pgd) (0) > > > > #else > > > > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > > > > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > > > > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > > > > #endif > > > > > > > > #ifdef __PAGETABLE_PUD_FOLDED > > > > #define __p4d_none(early, p4d) (0) > > > > #else > > > > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > > > > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > > > > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > > > > #endif > > > > > > > > #ifdef __PAGETABLE_PMD_FOLDED > > > > #define __pud_none(early, pud) (0) > > > > #else > > > > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > > > > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > > > > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > > > > #endif > > > > > > > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > > > > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > > > > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > > > > > > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > > > > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > > > > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > > > > > > > static void *mem_to_shadow(const void *addr) > > > > { > > > > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > > > > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > > > > } > > > > > > > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > > > > + > > > > + do { > > > > + next = addr + PAGE_SIZE; > > > > + if (__pte_none(false, ptep_get(ptep))) > > > > + pte_clear(&init_mm, addr, ptep); > > > > + } while (ptep++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pmd_t *pmdp = pmd_offset(pudp, addr); > > > > + > > > > + do { > > > > + next = pmd_addr_end(addr, end); > > > > + > > > > + if (__pmd_none(false, pmdp_get(pmdp))) { > > > > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > > > > + kernel_pte_init(__va(pte_phys)); > > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > > > > + sizeof(kasan_early_shadow_pte)); > > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > > + } > > > > + } > > > > + kasan_shallow_populate_pte(pmdp, addr, next, node); > > > > + } while (pmdp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pud_t *pudp = pud_offset(p4dp, addr); > > > > + > > > > + do { > > > > + next = pud_addr_end(addr, end); > > > > + > > > > + if (__pud_none(false, pudp_get(pudp))) { > > > > + > > > > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > > > > +#ifndef __PAGETABLE_PMD_FOLDED > > > > + pmd_init(__va(pmd_phys)); > > > > +#endif > > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > > > > + sizeof(kasan_early_shadow_pmd)); > > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > > + > > > > + } > > > > + } > > > > + > > > > + kasan_shallow_populate_pmd(pudp, addr, next, node); > > > > + } while (pudp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + p4d_t *p4dp = p4d_offset(pgdp, addr); > > > > + > > > > + do { > > > > + next = p4d_addr_end(addr, end); > > > > + > > > > + if (__p4d_none(false, p4dp_get(p4dp))) { > > > > + > > > > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > > > > +#ifndef __PAGETABLE_PUD_FOLDED > > > > + pud_init(__va(pud_phys)); > > > > +#endif > > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > > > > + sizeof(kasan_early_shadow_pud)); > > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > > + } > > > > + } > > > > + kasan_shallow_populate_pud(p4dp, addr, next, node); > > > > + } while (p4dp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pgd_t *pgdp; > > > > + > > > > + pgdp = pgd_offset_k(addr); > > > > + > > > > + do { > > > > + next = pgd_addr_end(addr, end); > > > > + > > > > + if (__pgd_none(false, pgdp_get(pgdp))) { > > > > + > > > > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > > > > + pgd_init(__va(p4d_phys)); > > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > > > > + sizeof(kasan_early_shadow_p4d)); > > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > > + } > > > > + } > > > > + > > > > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > > > > + } while (pgdp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > > > > +{ > > > > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > > > > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > > > > + > > > > + kasan_shallow_populate_pgd(vaddr, vend, node); > > > > +} > > > > + > > > > +#ifdef CONFIG_KASAN_VMALLOC > > > > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > > > > +{ > > > > + unsigned long shadow_start, shadow_end; > > > > + > > > > + if (!is_vmalloc_or_module_addr(start)) > > > > + return; > > > > + > > > > + shadow_start = (unsigned long)mem_to_shadow(start); > > > > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > > > > + shadow_end = (unsigned long)mem_to_shadow(start + size); > > > > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > > > > + > > > > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > > > > +} > > > > +#endif > > > > + > > > > asmlinkage void __init kasan_early_init(void) > > > > { > > > > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > > > > @@ -297,9 +451,6 @@ void __init kasan_init(void) > > > > /* Maps everything to a single page of zeroes */ > > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > > > > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > > - mem_to_shadow((void *)KFENCE_AREA_END)); > > > > - > > > > /* Populate the linear mapping */ > > > > for_each_mem_range(i, &pa_start, &pa_end) { > > > > void *start = (void *)phys_to_virt(pa_start); > > > > @@ -309,9 +460,9 @@ void __init kasan_init(void) > > > > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > > > > } > > > > > > > > - /* Populate modules mapping */ > > > > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > > > > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > > > > + > > > > /* > > > > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > > > > * should make sure that it maps the zero page read-only. > > > > -- > > > > 2.43.0 > > > > > > > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-20 7:47 ` Huacai Chen @ 2026-10-20 9:40 ` haoran.jiang 2026-10-20 10:46 ` Huacai Chen 0 siblings, 1 reply; 8+ messages in thread From: haoran.jiang @ 2026-10-20 9:40 UTC (permalink / raw) To: Huacai Chen; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang 2026年10月20日 15:47, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > On Tue, Oct 20, 2026 at 10:37 AM <haoran.jiang@linux.dev> wrote: > > > > > 2026年10月17日 16:24, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > On Fri, Oct 16, 2026 at 11:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > 2026年10月14日 12:43, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > Hi, Haoran, > > > > > > On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > > > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > > > For the page table of the VMALLOC shadow region, > > > > only a partial page table needs to be established, > > > > allowing the complete page table entries to be > > > > populated on demand during use. > > > > > > > > --- > > > > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > > > > page tables are established for the modules and vmalloc regions, > > > > but the page tables are not fully populated; the complete page > > > > tables are established automatically when vmalloc is used. > > > > > > > Why also change modules together with vmalloc regions? Because they > > > can both alloc/free dynamically, and the current approch can only > > > handle "one time" for modules? > > > > > > The shadow memory for modules regions is the same as that of vmalloc regions, > > > full page tables can be built on demand during usage. Memory is allocated > > > when loading modules, and the shadow memory of the allocated memory establishes > > > complete page tables via `kasan_populate_vmalloc_do` > > > (load_module->execmem_alloc->kasan_populate_vmalloc_do), similar to riscv and arm64. > > > > > The current code can also handle modules, so I want to know the > > difference. I guess the current code can only handle a module loading > > once, but this patch makes it possible to load/unload modules > > repeatedly. But I'm not sure. Anyway, I don't want something to become > > worse. > > > > 1,With the current approach, the shadow page tables for the modules > > region are established via `kasan_map_populate` and `KASAN_VMALLOC=n`. > > A test module performs an out-of-bounds write to a global array. After > > repeated `insmod`/`rmmod` cycles, the test module reports false positives. > > This is because the current flow does not properly clean up the shadow data. > > > > Taking x86 as an example, since x86 can be configured with KASAN_VMALLOC=n, > > when KASAN_VMALLOC=n, the correct flow is as follows: > > > x86 has some trouble implementing KASAN_VMALLOC? Or does x86's > solution have its advantage? > > Huacai > On x86, KASAN_VMALLOC is a configurable option; both implementations are supported. > > > > execmem_arch_setup -> flags = EXECMEM_KASAN_SHADOW, the flag is set to EXECMEM_KASAN_SHADOW, > > and the subsequent flow should select the correct path based on the EXECMEM_KASAN_SHADOW flag. > > > > insmod -> execmem_vmalloc -> (kasan && (kasan_alloc_module_shadow(p, size, GFP_KERNEL) > > -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow > > -> vfree(kasan_mem_to_shadow(vm->addr)); > > > > This flow it establishes the shadow page tables, then poisons them, > > and cleans up the shadow data when the module is unloaded. > > > > However, the current situation on LoongArch is as follows: > > > > execmem_arch_setup -> does not set EXECMEM_KASAN_SHADOW > > > > insmod -> execmem_vmalloc -> does not call kasan_alloc_module_shadow > > -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow > > -> does not call vfree(kasan_mem_to_shadow(vm->addr)); > > > > LoongArch does not set EXECMEM_KASAN_SHADOW during initialization, which > > causes the shadow data not to be properly cleaned up at unload time, > > potentially leading to false positives the next time a module is loaded. > > > > On RISC-V/arm64, KASAN_VMALLOC is enabled by default, and the shadow page > > tables for the module region are established and torn down via > > kasan_populate_vmalloc_do/kasan_release_vmalloc. This is exactly > > the approach that kasan_shallow_populate aims to implement. > > > > 2,kasan_shallow_populate requires fewer page tables than kasan_map_populate, > > and it can also save memory during initialization. > > > > > > > > > > > > > If the address is pgd-aligned, the page table is established > > > > down to the pgd level; if it is pud-aligned, it is established > > > > down to the pud level, and so on. While establishing the > > > > /pgd/pud/pmd levels, the page table entries must be initialized > > > > to none. For example, a pmd entry of none is an invalid_pte_table. > > > > Only in this way can the correct page tables be established at > > > > use time. At the same time, boundary issues must be considered > > > > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > > > > and mem_to_shadow(vmemmap) are in the same pmd, and the region > > > > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > > > > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > > > > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > > > > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. > > > > > > > kasan_pgd_populate(early) populate the page table to early shadow > > > page, kasan_pgd_populate(!early) populate the page table to normal > > > shadow page, and kasan_shallow_populate() populate the page table to > > > invalid_{pud,pmd,pte}_table? > > > > > > It seems you are do similar work as RISCV, and RISCV use three APIs > > > for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch > > > use the same API for the 1st and 2nd purpose, can we add a "shallow" > > > parameter to kasan_pgd_populate() so we can also handle the 3th > > > purpose? > > > > > > Yes. `kasan_shallow_populate()` builds incomplete shadow page tables for vmalloc and modules > > > regions and needs to handle boundaries. As an example: > > > `mem_to_shadow(VMALLOC_END)` and `mem_to_shadow(vmemmap)` share one PTE table. > > > The PTE entry for `mem_to_shadow(VMALLOC_END)` must be none, whereas the PTE entry > > > for `mem_to_shadow(vmemmap)` points to `early_shadow_page`. The final page table > > > layout is shown below > > > > > > pte +------ > > > +------------------+ > > > |early_shadow_page | > > > pmd +------------------+ > > > +----------------+ |early_shadow_page | > > > |early_shadow_pte| +------------------+ > > > +----------------+ | global | > > > | ptep |-----------+------------------+ > > > +----------------+ > > > | ... | pte > > > +----------------+ +--------------+ > > > | ... | | global | > > > pgd +----------------+ +--------------+ > > > +------------------+ | ptep |---------------| global | > > > | pmdp +----------+----------------+ +--------------+ > > > +------------------+ pmd > > > | pmdp |----------+--------------+ > > > +------------------+ |invalid_pte | > > > | | +--------------+ > > > | ... | |invalid_pte | > > > | | +--------------+ > > > +------------------+ > > > | ... | > > > +------------------+ pmd > > > | ... | +------------+ > > > +------------------+ | invalid_pte| > > > | ... | +------------+ > > > +------------------+ | invalid_pte| > > > | pmdp |---------- +------------+ > > > +------------------+ > > > | ... | > > > +------------------+ > > > > > > `kasan_pgd_populate()` can handle the third case by adding a parameter. I will implement this in the next version. > > > > > > > > > > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > > > > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > > > > Other architectures such as arm64/riscv need kasan_populate_early_shadow > > > > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > > > > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > > > > page again. On LoongArch, however, the entire shadow space is first mapped to > > > > page 0 via kasan_pgd_populate, and then page tables are established for the > > > > required regions, which is simpler and clearer. > > > > > > > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > > > > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > > > > because kasan_populate_early_vm_area_shadow requires them. > > > > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > > > > page table mappings for the early vmalloc regions before vmalloc_init. > > > > At this point, complete page tables need to be established, but > > > > kasan_populate_early_vm_area_shadow is called after step 1, at which time > > > > > > > I'm a little confusing here. What is "step 1" here? > > > > > > Step 1 refers to `kasan_shallow_populate()`. The page tables created after > > > `kasan_shallow_populate()` are shown in the figure above. We need to check > > > for empty entries using /pgd_none/p4d_none/pmd_none/pone_pte(). Only then > > > can `kasan_map_populate()` build the full page tables. > > > `kasan_populate_early_vm_area_shadow()` runs after > > > `kasan_shallow_populate()` but before `vmalloc_init()`. > > > At this stage, page tables for vmalloc regions cannot be automatically > > > populated on demand, so we need to build full page tables here. > > > > > Then it is better to use `kasan_shallow_populate()` rather than `step 1`. > > > > Huacai > > > > > > > > > > > > > only incomplete page tables have been established for the vmalloc region. > > > > The condition for a page table entry to be none requires the > > > > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > > > > whether an entry is none can the complete page tables be established properly. > > > > > > > > Test: > > > > > > > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > > > > > > > CONFIG_KUNIT=y > > > > > > > > CONFIG_KASAN_KUNIT_TEST=m > > > > > > > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > > > > > > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > > > > > > > before this patch: > > > > > > > > ... > > > > > > > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > > > > > > > ... > > > > > > > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > > > > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > > > > [ 504.016171] ok 1 kasan > > > > > > > > after this patch: > > > > > > > > ... > > > > > > > > [ 531.630760] ok 68 vmalloc_oob > > > > > > > > ... > > > > > > > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > > > > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > > > > [ 532.891219] ok 1 kasan > > > > > > > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > > > --- > > > > arch/loongarch/Kconfig | 2 + > > > > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > > > > 2 files changed, 164 insertions(+), 11 deletions(-) > > > > > > > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > > > > index 2067d1f2ad7a..c95c6fd8a138 100644 > > > > --- a/arch/loongarch/Kconfig > > > > +++ b/arch/loongarch/Kconfig > > > > @@ -119,6 +119,7 @@ config LOONGARCH > > > > select HAVE_ARCH_JUMP_LABEL > > > > select HAVE_ARCH_JUMP_LABEL_RELATIVE > > > > select HAVE_ARCH_KASAN if 64BIT > > > > + select HAVE_ARCH_KASAN_VMALLOC > > > > > > > I think this should be limited by 64BIT. > > > > > > Huacai > > > > > > I will fix this in the next revision. > > > > > > > > > > > select HAVE_ARCH_KFENCE if 64BIT > > > > select HAVE_ARCH_KGDB if PERF_EVENTS > > > > select HAVE_ARCH_KSTACK_ERASE > > > > @@ -187,6 +188,7 @@ config LOONGARCH > > > > select HOTPLUG_SMT if HOTPLUG_CPU > > > > select IRQ_FORCED_THREADING > > > > select IRQ_LOONGARCH_CPU > > > > + select KASAN_VMALLOC if KASAN > > > > select LOCK_MM_AND_FIND_VMA > > > > select MMU_GATHER_MERGE_VMAS if MMU > > > > select MMU_GATHER_RCU_TABLE_FREE > > > > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > > > > index 92ca0ba86406..962c37316200 100644 > > > > --- a/arch/loongarch/mm/kasan_init.c > > > > +++ b/arch/loongarch/mm/kasan_init.c > > > > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > > > > #define __pgd_none(early, pgd) (0) > > > > #else > > > > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > > > > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > > > > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > > > > #endif > > > > > > > > #ifdef __PAGETABLE_PUD_FOLDED > > > > #define __p4d_none(early, p4d) (0) > > > > #else > > > > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > > > > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > > > > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > > > > #endif > > > > > > > > #ifdef __PAGETABLE_PMD_FOLDED > > > > #define __pud_none(early, pud) (0) > > > > #else > > > > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > > > > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > > > > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > > > > #endif > > > > > > > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > > > > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > > > > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > > > > > > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > > > > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > > > > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > > > > > > > static void *mem_to_shadow(const void *addr) > > > > { > > > > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > > > > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > > > > } > > > > > > > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > > > > + > > > > + do { > > > > + next = addr + PAGE_SIZE; > > > > + if (__pte_none(false, ptep_get(ptep))) > > > > + pte_clear(&init_mm, addr, ptep); > > > > + } while (ptep++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pmd_t *pmdp = pmd_offset(pudp, addr); > > > > + > > > > + do { > > > > + next = pmd_addr_end(addr, end); > > > > + > > > > + if (__pmd_none(false, pmdp_get(pmdp))) { > > > > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > > > > + kernel_pte_init(__va(pte_phys)); > > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > > > > + sizeof(kasan_early_shadow_pte)); > > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > > + } > > > > + } > > > > + kasan_shallow_populate_pte(pmdp, addr, next, node); > > > > + } while (pmdp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pud_t *pudp = pud_offset(p4dp, addr); > > > > + > > > > + do { > > > > + next = pud_addr_end(addr, end); > > > > + > > > > + if (__pud_none(false, pudp_get(pudp))) { > > > > + > > > > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > > > > +#ifndef __PAGETABLE_PMD_FOLDED > > > > + pmd_init(__va(pmd_phys)); > > > > +#endif > > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > > > > + sizeof(kasan_early_shadow_pmd)); > > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > > + > > > > + } > > > > + } > > > > + > > > > + kasan_shallow_populate_pmd(pudp, addr, next, node); > > > > + } while (pudp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + p4d_t *p4dp = p4d_offset(pgdp, addr); > > > > + > > > > + do { > > > > + next = p4d_addr_end(addr, end); > > > > + > > > > + if (__p4d_none(false, p4dp_get(p4dp))) { > > > > + > > > > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > > > > +#ifndef __PAGETABLE_PUD_FOLDED > > > > + pud_init(__va(pud_phys)); > > > > +#endif > > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > > > > + sizeof(kasan_early_shadow_pud)); > > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > > + } > > > > + } > > > > + kasan_shallow_populate_pud(p4dp, addr, next, node); > > > > + } while (p4dp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > > > > + unsigned long end, int node) > > > > +{ > > > > + unsigned long next; > > > > + pgd_t *pgdp; > > > > + > > > > + pgdp = pgd_offset_k(addr); > > > > + > > > > + do { > > > > + next = pgd_addr_end(addr, end); > > > > + > > > > + if (__pgd_none(false, pgdp_get(pgdp))) { > > > > + > > > > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > > > > + > > > > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > > > > + pgd_init(__va(p4d_phys)); > > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > > + continue; > > > > + } else { > > > > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > > > > + sizeof(kasan_early_shadow_p4d)); > > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > > + } > > > > + } > > > > + > > > > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > > > > + } while (pgdp++, addr = next, addr != end); > > > > +} > > > > + > > > > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > > > > +{ > > > > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > > > > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > > > > + > > > > + kasan_shallow_populate_pgd(vaddr, vend, node); > > > > +} > > > > + > > > > +#ifdef CONFIG_KASAN_VMALLOC > > > > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > > > > +{ > > > > + unsigned long shadow_start, shadow_end; > > > > + > > > > + if (!is_vmalloc_or_module_addr(start)) > > > > + return; > > > > + > > > > + shadow_start = (unsigned long)mem_to_shadow(start); > > > > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > > > > + shadow_end = (unsigned long)mem_to_shadow(start + size); > > > > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > > > > + > > > > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > > > > +} > > > > +#endif > > > > + > > > > asmlinkage void __init kasan_early_init(void) > > > > { > > > > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > > > > @@ -297,9 +451,6 @@ void __init kasan_init(void) > > > > /* Maps everything to a single page of zeroes */ > > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > > > > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > > - mem_to_shadow((void *)KFENCE_AREA_END)); > > > > - > > > > /* Populate the linear mapping */ > > > > for_each_mem_range(i, &pa_start, &pa_end) { > > > > void *start = (void *)phys_to_virt(pa_start); > > > > @@ -309,9 +460,9 @@ void __init kasan_init(void) > > > > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > > > > } > > > > > > > > - /* Populate modules mapping */ > > > > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > > > > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > > > > + > > > > /* > > > > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > > > > * should make sure that it maps the zero page read-only. > > > > -- > > > > 2.43.0 > > > > > > > > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] LoongArch: add KASAN_VMALLOC support 2026-10-20 9:40 ` haoran.jiang @ 2026-10-20 10:46 ` Huacai Chen 0 siblings, 0 replies; 8+ messages in thread From: Huacai Chen @ 2026-10-20 10:46 UTC (permalink / raw) To: haoran.jiang; +Cc: loongarch, kasan-dev, linux-kernel, kernel, Haoran Jiang On Tue, Oct 20, 2026 at 5:40 PM <haoran.jiang@linux.dev> wrote: > > 2026年10月20日 15:47, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > On Tue, Oct 20, 2026 at 10:37 AM <haoran.jiang@linux.dev> wrote: > > > > > > > > 2026年10月17日 16:24, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > On Fri, Oct 16, 2026 at 11:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > > > > 2026年10月14日 12:43, "Huacai Chen" <chenhuacai@kernel.org mailto:chenhuacai@kernel.org?to=%22Huacai%20Chen%22%20%3Cchenhuacai%40kernel.org%3E > 写到: > > > > > > > > Hi, Haoran, > > > > > > > > On Mon, Oct 12, 2026 at 4:05 PM <haoran.jiang@linux.dev> wrote: > > > > > > > > > > > > > > From: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > > > > > For the page table of the VMALLOC shadow region, > > > > > only a partial page table needs to be established, > > > > > allowing the complete page table entries to be > > > > > populated on demand during use. > > > > > > > > > > --- > > > > > 1,Through the kasan_shallow_populate_pgd/p4d/pud/pmd/pte functions, > > > > > page tables are established for the modules and vmalloc regions, > > > > > but the page tables are not fully populated; the complete page > > > > > tables are established automatically when vmalloc is used. > > > > > > > > > Why also change modules together with vmalloc regions? Because they > > > > can both alloc/free dynamically, and the current approch can only > > > > handle "one time" for modules? > > > > > > > > The shadow memory for modules regions is the same as that of vmalloc regions, > > > > full page tables can be built on demand during usage. Memory is allocated > > > > when loading modules, and the shadow memory of the allocated memory establishes > > > > complete page tables via `kasan_populate_vmalloc_do` > > > > (load_module->execmem_alloc->kasan_populate_vmalloc_do), similar to riscv and arm64. > > > > > > > The current code can also handle modules, so I want to know the > > > difference. I guess the current code can only handle a module loading > > > once, but this patch makes it possible to load/unload modules > > > repeatedly. But I'm not sure. Anyway, I don't want something to become > > > worse. > > > > > > 1,With the current approach, the shadow page tables for the modules > > > region are established via `kasan_map_populate` and `KASAN_VMALLOC=n`. > > > A test module performs an out-of-bounds write to a global array. After > > > repeated `insmod`/`rmmod` cycles, the test module reports false positives. > > > This is because the current flow does not properly clean up the shadow data. > > > > > > Taking x86 as an example, since x86 can be configured with KASAN_VMALLOC=n, > > > when KASAN_VMALLOC=n, the correct flow is as follows: > > > > > x86 has some trouble implementing KASAN_VMALLOC? Or does x86's > > solution have its advantage? > > > > Huacai > > > > On x86, KASAN_VMALLOC is a configurable option; both implementations are supported. OK, then is the kasan_populate_vmalloc/kasan_release_vmalloc solution better than kasan_alloc_module_shadow/kasan_free_module_shadow solution for modules? Huacai > > > > > > > execmem_arch_setup -> flags = EXECMEM_KASAN_SHADOW, the flag is set to EXECMEM_KASAN_SHADOW, > > > and the subsequent flow should select the correct path based on the EXECMEM_KASAN_SHADOW flag. > > > > > > insmod -> execmem_vmalloc -> (kasan && (kasan_alloc_module_shadow(p, size, GFP_KERNEL) > > > -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow > > > -> vfree(kasan_mem_to_shadow(vm->addr)); > > > > > > This flow it establishes the shadow page tables, then poisons them, > > > and cleans up the shadow data when the module is unloaded. > > > > > > However, the current situation on LoongArch is as follows: > > > > > > execmem_arch_setup -> does not set EXECMEM_KASAN_SHADOW > > > > > > insmod -> execmem_vmalloc -> does not call kasan_alloc_module_shadow > > > -> register_global -> kasan_poison-> rmmod -> kasan_free_module_shadow > > > -> does not call vfree(kasan_mem_to_shadow(vm->addr)); > > > > > > LoongArch does not set EXECMEM_KASAN_SHADOW during initialization, which > > > causes the shadow data not to be properly cleaned up at unload time, > > > potentially leading to false positives the next time a module is loaded. > > > > > > On RISC-V/arm64, KASAN_VMALLOC is enabled by default, and the shadow page > > > tables for the module region are established and torn down via > > > kasan_populate_vmalloc_do/kasan_release_vmalloc. This is exactly > > > the approach that kasan_shallow_populate aims to implement. > > > > > > 2,kasan_shallow_populate requires fewer page tables than kasan_map_populate, > > > and it can also save memory during initialization. > > > > > > > > > > > > > > > > > If the address is pgd-aligned, the page table is established > > > > > down to the pgd level; if it is pud-aligned, it is established > > > > > down to the pud level, and so on. While establishing the > > > > > /pgd/pud/pmd levels, the page table entries must be initialized > > > > > to none. For example, a pmd entry of none is an invalid_pte_table. > > > > > Only in this way can the correct page tables be established at > > > > > use time. At the same time, boundary issues must be considered > > > > > when the address is not aligned. For example, mem_to_shadow(MALLOC_END) > > > > > and mem_to_shadow(vmemmap) are in the same pmd, and the region > > > > > [mem_to_shadow(vmemmap), mem_to_shadow(VMEMMAP_END)] needs to be > > > > > mapped to page 0. To avoid affecting the mem_to_shadow(vmemmap) region, > > > > > only the pmd entries for [mem_to_shadow(MALLOC_END) & PMD_MASK, mem_to_shadow(MALLOC_END)] > > > > > need to be initialized to invalid_pte_table. The remaining pmd entries are left as they are. > > > > > > > > > kasan_pgd_populate(early) populate the page table to early shadow > > > > page, kasan_pgd_populate(!early) populate the page table to normal > > > > shadow page, and kasan_shallow_populate() populate the page table to > > > > invalid_{pud,pmd,pte}_table? > > > > > > > > It seems you are do similar work as RISCV, and RISCV use three APIs > > > > for this purpose: kasan_{,early,shallow}_populate_pgd(). But LoongArch > > > > use the same API for the 1st and 2nd purpose, can we add a "shallow" > > > > parameter to kasan_pgd_populate() so we can also handle the 3th > > > > purpose? > > > > > > > > Yes. `kasan_shallow_populate()` builds incomplete shadow page tables for vmalloc and modules > > > > regions and needs to handle boundaries. As an example: > > > > `mem_to_shadow(VMALLOC_END)` and `mem_to_shadow(vmemmap)` share one PTE table. > > > > The PTE entry for `mem_to_shadow(VMALLOC_END)` must be none, whereas the PTE entry > > > > for `mem_to_shadow(vmemmap)` points to `early_shadow_page`. The final page table > > > > layout is shown below > > > > > > > > pte +------ > > > > +------------------+ > > > > |early_shadow_page | > > > > pmd +------------------+ > > > > +----------------+ |early_shadow_page | > > > > |early_shadow_pte| +------------------+ > > > > +----------------+ | global | > > > > | ptep |-----------+------------------+ > > > > +----------------+ > > > > | ... | pte > > > > +----------------+ +--------------+ > > > > | ... | | global | > > > > pgd +----------------+ +--------------+ > > > > +------------------+ | ptep |---------------| global | > > > > | pmdp +----------+----------------+ +--------------+ > > > > +------------------+ pmd > > > > | pmdp |----------+--------------+ > > > > +------------------+ |invalid_pte | > > > > | | +--------------+ > > > > | ... | |invalid_pte | > > > > | | +--------------+ > > > > +------------------+ > > > > | ... | > > > > +------------------+ pmd > > > > | ... | +------------+ > > > > +------------------+ | invalid_pte| > > > > | ... | +------------+ > > > > +------------------+ | invalid_pte| > > > > | pmdp |---------- +------------+ > > > > +------------------+ > > > > | ... | > > > > +------------------+ > > > > > > > > `kasan_pgd_populate()` can handle the third case by adding a parameter. I will implement this in the next version. > > > > > > > > > > > > > > 2,The removal of kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > > > mem_to_shadow((void *)KFENCE_AREA_END)) achieves the same effect as > > > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > > both map this region to a shared zero page. (KASAN_SHADOW_START, KASAN_SHADOW_END) > > > > > already covers (mem_to_shadow((void *)VMALLOC_START), mem_to_shadow((void *)KFENCE_AREA_END)). > > > > > Other architectures such as arm64/riscv need kasan_populate_early_shadow > > > > > because clear_shadow(KASAN_SHADOW_START, KASAN_SHADOW_END) is performed before > > > > > kasan_populate_early_shadow, so the reserved region must be mapped to the zero > > > > > page again. On LoongArch, however, the entire shadow space is first mapped to > > > > > page 0 via kasan_pgd_populate, and then page tables are established for the > > > > > required regions, which is simpler and clearer. > > > > > > > > > > 3,pgd_none/p4d_none/pmd_none/pte_none were added to the definitions of > > > > > __pgd_none/__p4d_none/__pud_none/__pmd_none/__pte_none respectively > > > > > because kasan_populate_early_vm_area_shadow requires them. > > > > > kasan_populate_early_vm_area_shadow establishes complete KASAN shadow > > > > > page table mappings for the early vmalloc regions before vmalloc_init. > > > > > At this point, complete page tables need to be established, but > > > > > kasan_populate_early_vm_area_shadow is called after step 1, at which time > > > > > > > > > I'm a little confusing here. What is "step 1" here? > > > > > > > > Step 1 refers to `kasan_shallow_populate()`. The page tables created after > > > > `kasan_shallow_populate()` are shown in the figure above. We need to check > > > > for empty entries using /pgd_none/p4d_none/pmd_none/pone_pte(). Only then > > > > can `kasan_map_populate()` build the full page tables. > > > > `kasan_populate_early_vm_area_shadow()` runs after > > > > `kasan_shallow_populate()` but before `vmalloc_init()`. > > > > At this stage, page tables for vmalloc regions cannot be automatically > > > > populated on demand, so we need to build full page tables here. > > > > > > > Then it is better to use `kasan_shallow_populate()` rather than `step 1`. > > > > > > Huacai > > > > > > > > > > > > > > > > > only incomplete page tables have been established for the vmalloc region. > > > > > The condition for a page table entry to be none requires the > > > > > pgd_none/p4d_none/pmd_none/pte_none checks. Only by correctly determining > > > > > whether an entry is none can the complete page tables be established properly. > > > > > > > > > > Test: > > > > > > > > > > https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson.git > > > > > > > > > > CONFIG_KUNIT=y > > > > > > > > > > CONFIG_KASAN_KUNIT_TEST=m > > > > > > > > > > Add `kunit.enable=1 kasan_multi_shot` to the cmdline. > > > > > > > > > > Test with 3-level 16K pages and 4-level 4K pages respectively. > > > > > > > > > > before this patch: > > > > > > > > > > ... > > > > > > > > > > [ 502.760009] ok 68 vmalloc_oob # SKIP Test requires CONFIG_KASAN_VMALLOC=y > > > > > > > > > > ... > > > > > > > > > > [ 504.016000] # kasan: pass:49 fail:0 skip:26 total:75 > > > > > [ 504.016092] # Totals: pass:49 fail:0 skip:26 total:75 > > > > > [ 504.016171] ok 1 kasan > > > > > > > > > > after this patch: > > > > > > > > > > ... > > > > > > > > > > [ 531.630760] ok 68 vmalloc_oob > > > > > > > > > > ... > > > > > > > > > > [ 532.891057] # kasan: pass:50 fail:0 skip:25 total:75 > > > > > [ 532.891142] # Totals: pass:50 fail:0 skip:25 total:75 > > > > > [ 532.891219] ok 1 kasan > > > > > > > > > > Signed-off-by: Haoran Jiang <jianghaoran@kylinos.cn> > > > > > > > > > > --- > > > > > arch/loongarch/Kconfig | 2 + > > > > > arch/loongarch/mm/kasan_init.c | 173 ++++++++++++++++++++++++++++++--- > > > > > 2 files changed, 164 insertions(+), 11 deletions(-) > > > > > > > > > > diff --git a/arch/loongarch/Kconfig b/arch/loongarch/Kconfig > > > > > index 2067d1f2ad7a..c95c6fd8a138 100644 > > > > > --- a/arch/loongarch/Kconfig > > > > > +++ b/arch/loongarch/Kconfig > > > > > @@ -119,6 +119,7 @@ config LOONGARCH > > > > > select HAVE_ARCH_JUMP_LABEL > > > > > select HAVE_ARCH_JUMP_LABEL_RELATIVE > > > > > select HAVE_ARCH_KASAN if 64BIT > > > > > + select HAVE_ARCH_KASAN_VMALLOC > > > > > > > > > I think this should be limited by 64BIT. > > > > > > > > Huacai > > > > > > > > I will fix this in the next revision. > > > > > > > > > > > > > > select HAVE_ARCH_KFENCE if 64BIT > > > > > select HAVE_ARCH_KGDB if PERF_EVENTS > > > > > select HAVE_ARCH_KSTACK_ERASE > > > > > @@ -187,6 +188,7 @@ config LOONGARCH > > > > > select HOTPLUG_SMT if HOTPLUG_CPU > > > > > select IRQ_FORCED_THREADING > > > > > select IRQ_LOONGARCH_CPU > > > > > + select KASAN_VMALLOC if KASAN > > > > > select LOCK_MM_AND_FIND_VMA > > > > > select MMU_GATHER_MERGE_VMAS if MMU > > > > > select MMU_GATHER_RCU_TABLE_FREE > > > > > diff --git a/arch/loongarch/mm/kasan_init.c b/arch/loongarch/mm/kasan_init.c > > > > > index 92ca0ba86406..962c37316200 100644 > > > > > --- a/arch/loongarch/mm/kasan_init.c > > > > > +++ b/arch/loongarch/mm/kasan_init.c > > > > > @@ -17,28 +17,28 @@ static pgd_t kasan_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); > > > > > #define __pgd_none(early, pgd) (0) > > > > > #else > > > > > #define __pgd_none(early, pgd) (early ? (pgd_val(pgd) == 0) : \ > > > > > -(__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d))) > > > > > +((__pa(pgd_val(pgd)) == (unsigned long)__pa(kasan_early_shadow_p4d)) || pgd_none(pgd))) > > > > > #endif > > > > > > > > > > #ifdef __PAGETABLE_PUD_FOLDED > > > > > #define __p4d_none(early, p4d) (0) > > > > > #else > > > > > #define __p4d_none(early, p4d) (early ? (p4d_val(p4d) == 0) : \ > > > > > -(__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud))) > > > > > +((__pa(p4d_val(p4d)) == (unsigned long)__pa(kasan_early_shadow_pud)) || p4d_none(p4d))) > > > > > #endif > > > > > > > > > > #ifdef __PAGETABLE_PMD_FOLDED > > > > > #define __pud_none(early, pud) (0) > > > > > #else > > > > > #define __pud_none(early, pud) (early ? (pud_val(pud) == 0) : \ > > > > > -(__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd))) > > > > > +((__pa(pud_val(pud)) == (unsigned long)__pa(kasan_early_shadow_pmd)) || pud_none(pud))) > > > > > #endif > > > > > > > > > > #define __pmd_none(early, pmd) (early ? (pmd_val(pmd) == 0) : \ > > > > > -(__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte))) > > > > > +((__pa(pmd_val(pmd)) == (unsigned long)__pa(kasan_early_shadow_pte)) || pmd_none(pmd))) > > > > > > > > > > #define __pte_none(early, pte) (early ? pte_none(pte) : \ > > > > > -((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page))) > > > > > +(((pte_val(pte) & _PFN_MASK) == (unsigned long)__pa(kasan_early_shadow_page)) || pte_none(pte))) > > > > > > > > > > static void *mem_to_shadow(const void *addr) > > > > > { > > > > > @@ -242,6 +242,160 @@ static void __init kasan_map_populate(unsigned long start, unsigned long end, > > > > > kasan_pgd_populate(start & PAGE_MASK, PAGE_ALIGN(end), node, false); > > > > > } > > > > > > > > > > +static void __init kasan_shallow_populate_pte(pmd_t *pmdp, unsigned long addr, > > > > > + unsigned long end, int node) > > > > > +{ > > > > > + unsigned long next; > > > > > + pte_t *ptep = pte_offset_kernel(pmdp, addr); > > > > > + > > > > > + do { > > > > > + next = addr + PAGE_SIZE; > > > > > + if (__pte_none(false, ptep_get(ptep))) > > > > > + pte_clear(&init_mm, addr, ptep); > > > > > + } while (ptep++, addr = next, addr != end); > > > > > +} > > > > > + > > > > > +static void __init kasan_shallow_populate_pmd(pud_t *pudp, unsigned long addr, > > > > > + unsigned long end, int node) > > > > > +{ > > > > > + unsigned long next; > > > > > + pmd_t *pmdp = pmd_offset(pudp, addr); > > > > > + > > > > > + do { > > > > > + next = pmd_addr_end(addr, end); > > > > > + > > > > > + if (__pmd_none(false, pmdp_get(pmdp))) { > > > > > + phys_addr_t pte_phys = kasan_alloc_zeroed_page(node); > > > > > + > > > > > + if (IS_ALIGNED(addr, PMD_SIZE) && end - addr >= PMD_SIZE) { > > > > > + kernel_pte_init(__va(pte_phys)); > > > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > > > + continue; > > > > > + } else { > > > > > + memcpy(__va(pte_phys), kasan_early_shadow_pte, > > > > > + sizeof(kasan_early_shadow_pte)); > > > > > + pmd_populate_kernel(NULL, pmdp, (pte_t *)__va(pte_phys)); > > > > > + } > > > > > + } > > > > > + kasan_shallow_populate_pte(pmdp, addr, next, node); > > > > > + } while (pmdp++, addr = next, addr != end); > > > > > +} > > > > > + > > > > > +static void __init kasan_shallow_populate_pud(p4d_t *p4dp, unsigned long addr, > > > > > + unsigned long end, int node) > > > > > +{ > > > > > + unsigned long next; > > > > > + pud_t *pudp = pud_offset(p4dp, addr); > > > > > + > > > > > + do { > > > > > + next = pud_addr_end(addr, end); > > > > > + > > > > > + if (__pud_none(false, pudp_get(pudp))) { > > > > > + > > > > > + phys_addr_t pmd_phys = kasan_alloc_zeroed_page(node); > > > > > + > > > > > + if (IS_ALIGNED(addr, PUD_SIZE) && end - addr >= PUD_SIZE) { > > > > > +#ifndef __PAGETABLE_PMD_FOLDED > > > > > + pmd_init(__va(pmd_phys)); > > > > > +#endif > > > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > > > + continue; > > > > > + } else { > > > > > + memcpy(__va(pmd_phys), kasan_early_shadow_pmd, > > > > > + sizeof(kasan_early_shadow_pmd)); > > > > > + pud_populate(&init_mm, pudp, (pmd_t *)__va(pmd_phys)); > > > > > + > > > > > + } > > > > > + } > > > > > + > > > > > + kasan_shallow_populate_pmd(pudp, addr, next, node); > > > > > + } while (pudp++, addr = next, addr != end); > > > > > +} > > > > > + > > > > > +static void __init kasan_shallow_populate_p4d(pgd_t *pgdp, unsigned long addr, > > > > > + unsigned long end, int node) > > > > > +{ > > > > > + unsigned long next; > > > > > + p4d_t *p4dp = p4d_offset(pgdp, addr); > > > > > + > > > > > + do { > > > > > + next = p4d_addr_end(addr, end); > > > > > + > > > > > + if (__p4d_none(false, p4dp_get(p4dp))) { > > > > > + > > > > > + phys_addr_t pud_phys = kasan_alloc_zeroed_page(node); > > > > > + > > > > > + if (IS_ALIGNED(addr, P4D_SIZE) && end - addr >= P4D_SIZE) { > > > > > +#ifndef __PAGETABLE_PUD_FOLDED > > > > > + pud_init(__va(pud_phys)); > > > > > +#endif > > > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > > > + continue; > > > > > + } else { > > > > > + memcpy(__va(pud_phys), kasan_early_shadow_pud, > > > > > + sizeof(kasan_early_shadow_pud)); > > > > > + p4d_populate(&init_mm, p4dp, (pud_t *)__va(pud_phys)); > > > > > + } > > > > > + } > > > > > + kasan_shallow_populate_pud(p4dp, addr, next, node); > > > > > + } while (p4dp++, addr = next, addr != end); > > > > > +} > > > > > + > > > > > +static void __init kasan_shallow_populate_pgd(unsigned long addr, > > > > > + unsigned long end, int node) > > > > > +{ > > > > > + unsigned long next; > > > > > + pgd_t *pgdp; > > > > > + > > > > > + pgdp = pgd_offset_k(addr); > > > > > + > > > > > + do { > > > > > + next = pgd_addr_end(addr, end); > > > > > + > > > > > + if (__pgd_none(false, pgdp_get(pgdp))) { > > > > > + > > > > > + phys_addr_t p4d_phys = kasan_alloc_zeroed_page(node); > > > > > + > > > > > + if (IS_ALIGNED(addr, PGDIR_SIZE) && end - addr >= PGDIR_SIZE) { > > > > > + pgd_init(__va(p4d_phys)); > > > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > > > + continue; > > > > > + } else { > > > > > + memcpy(__va(p4d_phys), kasan_early_shadow_p4d, > > > > > + sizeof(kasan_early_shadow_p4d)); > > > > > + pgd_populate(&init_mm, pgdp, (p4d_t *)__va(p4d_phys)); > > > > > + } > > > > > + } > > > > > + > > > > > + kasan_shallow_populate_p4d(pgdp, addr, next, node); > > > > > + } while (pgdp++, addr = next, addr != end); > > > > > +} > > > > > + > > > > > +static void __init kasan_shallow_populate(unsigned long start, unsigned long end, int node) > > > > > +{ > > > > > + unsigned long vaddr = (unsigned long)start & PAGE_MASK; > > > > > + unsigned long vend = PAGE_ALIGN((unsigned long)end); > > > > > + > > > > > + kasan_shallow_populate_pgd(vaddr, vend, node); > > > > > +} > > > > > + > > > > > +#ifdef CONFIG_KASAN_VMALLOC > > > > > +void __init kasan_populate_early_vm_area_shadow(void *start, unsigned long size) > > > > > +{ > > > > > + unsigned long shadow_start, shadow_end; > > > > > + > > > > > + if (!is_vmalloc_or_module_addr(start)) > > > > > + return; > > > > > + > > > > > + shadow_start = (unsigned long)mem_to_shadow(start); > > > > > + shadow_start = ALIGN_DOWN(shadow_start, PAGE_SIZE); > > > > > + shadow_end = (unsigned long)mem_to_shadow(start + size); > > > > > + shadow_end = ALIGN(shadow_end, PAGE_SIZE); > > > > > + > > > > > + kasan_map_populate(shadow_start, shadow_end, NUMA_NO_NODE); > > > > > +} > > > > > +#endif > > > > > + > > > > > asmlinkage void __init kasan_early_init(void) > > > > > { > > > > > BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, PGDIR_SIZE)); > > > > > @@ -297,9 +451,6 @@ void __init kasan_init(void) > > > > > /* Maps everything to a single page of zeroes */ > > > > > kasan_pgd_populate(KASAN_SHADOW_START, KASAN_SHADOW_END, NUMA_NO_NODE, true); > > > > > > > > > > - kasan_populate_early_shadow(mem_to_shadow((void *)VMALLOC_START), > > > > > - mem_to_shadow((void *)KFENCE_AREA_END)); > > > > > - > > > > > /* Populate the linear mapping */ > > > > > for_each_mem_range(i, &pa_start, &pa_end) { > > > > > void *start = (void *)phys_to_virt(pa_start); > > > > > @@ -309,9 +460,9 @@ void __init kasan_init(void) > > > > > (unsigned long)mem_to_shadow(end), NUMA_NO_NODE); > > > > > } > > > > > > > > > > - /* Populate modules mapping */ > > > > > - kasan_map_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > > > - (unsigned long)mem_to_shadow((void *)MODULES_END), NUMA_NO_NODE); > > > > > + kasan_shallow_populate((unsigned long)mem_to_shadow((void *)MODULES_VADDR), > > > > > + (unsigned long)mem_to_shadow((void *)VMALLOC_END), NUMA_NO_NODE); > > > > > + > > > > > /* > > > > > * KAsan may reuse the contents of kasan_early_shadow_pte directly, so we > > > > > * should make sure that it maps the zero page read-only. > > > > > -- > > > > > 2.43.0 > > > > > > > > > > > > > > ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-20 10:47 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-10-12 8:04 [PATCH] LoongArch: add KASAN_VMALLOC support haoran.jiang 2026-10-14 4:43 ` Huacai Chen 2026-10-16 15:04 ` haoran.jiang 2026-10-17 8:24 ` Huacai Chen 2026-10-20 2:37 ` haoran.jiang 2026-10-20 7:47 ` Huacai Chen 2026-10-20 9:40 ` haoran.jiang 2026-10-20 10:46 ` Huacai Chen
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.