From: haoran.jiang@linux.dev
To: "Huacai Chen" <chenhuacai@kernel.org>
Cc: loongarch@lists.linux.dev, kasan-dev@googlegroups.com,
linux-kernel@vger.kernel.org, kernel@xen0n.name,
"Haoran Jiang" <jianghaoran@kylinos.cn>
Subject: Re: [PATCH] LoongArch: add KASAN_VMALLOC support
Date: Fri, 16 Oct 2026 15:04:55 +0000 [thread overview]
Message-ID: <4c3a6be6aeef21fc2a19c3525f87485322c47a57@linux.dev> (raw)
In-Reply-To: <CAAhV-H5jO2HYT+64usWxCuDYu3oVLV__vkBL3J8MpOKEGKL_XQ@mail.gmail.com>
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
> >
>
next prev parent reply other threads:[~2026-10-16 15:04 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-12 8:04 haoran.jiang
2026-10-14 4:43 ` Huacai Chen
2026-10-16 15:04 ` haoran.jiang [this message]
2026-10-17 8:24 ` Huacai Chen
-- strict thread matches above, loose matches on Subject: below --
2026-10-12 7:59 haoran.jiang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4c3a6be6aeef21fc2a19c3525f87485322c47a57@linux.dev \
--to=haoran.jiang@linux.dev \
--cc=chenhuacai@kernel.org \
--cc=jianghaoran@kylinos.cn \
--cc=kasan-dev@googlegroups.com \
--cc=kernel@xen0n.name \
--cc=linux-kernel@vger.kernel.org \
--cc=loongarch@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®