Re: [PATCH] LoongArch: fix silent hang on kernel exec into an NX page
Song Hu <[email protected]> Thu, 16 Jul 2026 15:26:41 +0800
| Newsgroups | dev.linux.lists.loongarch,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
在 2026/7/16 14:24, Huacai Chen 写道: > Hi, Song, > > On Thu, Jul 16, 2026 at 11:02 AM Song Hu <[email protected]> wrote: >> spurious_fault() returns 1 for any non-write fault on a present page >> without checking the access permission, so a kernel instruction fetch into >> a present NX page (TLBNX) is treated as spurious and the faulting >> instruction re-executes forever -- a silent hang that bypasses >> fixup_exception() and the oops path. > Can you give a method to reproduce this hang? Hi Huacai, Thanks for the review. A late_initcall that allocates a page-table-backed executable page, flips it NX, then calls into it -- the TLBNX loops forever in spurious_fault() on the unpatched kernel. Built as obj-y: static int __init repro_init(void) { void (*fn)(void) = __vmalloc_node_range(PAGE_SIZE, PAGE_SIZE, VMALLOC_START, VMALLOC_END, GFP_KERNEL, PAGE_KERNEL_EXEC, VM_FLUSH_RESET_PERMS, NUMA_NO_NODE, __builtin_return_address(0)); if (!fn) return 0; set_memory_nx((unsigned long)fn, 1); fn(); /* TLBNX -> spurious_fault()=1 -> infinite fault loop */ return 0; } late_initcall(repro_init); The page must be page-table-backed (vmalloc range); an alloc_page()/DMW linear-map address isn't in the kernel page tables, so the walk in spurious_fault() never finds it. Tested in a qemu-system-loongarch64 guest (-machine virt -accel kvm -cpu la464) on a Loongson-3A6000 host : unpatched, the initcall never returns -- silent infinite TLBNX loop, no oops; with the fix, it oopses ("Unable to handle kernel paging request ... era == <addr>"). >> Recover the access type from the exception code: an exec fault (TLBNX) is >> spurious only if the page is executable, a read fault only if it is >> readable. Fold the pmd/pte check together and add pud_leaf/p4d_leaf. >> >> Fixes: 09cfefb7fa70c ("LoongArch: Add memory management") >> Signed-off-by: Song Hu <[email protected]> >> --- >> arch/loongarch/mm/fault.c | 24 ++++++++++++++++++++---- >> 1 file changed, 20 insertions(+), 4 deletions(-) >> >> diff --git a/arch/loongarch/mm/fault.c b/arch/loongarch/mm/fault.c >> index 2c93d33356e5..591a9c7060f2 100644 >> --- a/arch/loongarch/mm/fault.c >> +++ b/arch/loongarch/mm/fault.c >> @@ -38,6 +38,7 @@ static int __kprobes spurious_fault(unsigned long write, unsigned long address) >> pud_t *pud; >> pmd_t *pmd; >> pte_t *pte; >> + unsigned long prot; >> >> if (!(address & __UA_LIMIT)) >> return 0; >> @@ -58,15 +59,30 @@ static int __kprobes spurious_fault(unsigned long write, unsigned long address) >> if (!pmd_present(pmdp_get(pmd))) >> return 0; >> >> - if (pmd_leaf(*pmd)) { >> - return write ? pmd_write(pmdp_get(pmd)) : 1; >> + if (p4d_leaf(p4dp_get(p4d))) { >> + prot = p4d_val(p4dp_get(p4d)); >> + } else if (pud_leaf(pudp_get(pud))) { >> + prot = pud_val(pudp_get(pud)); >> + } else if (pmd_leaf(*pmd)) { >> + prot = pmd_val(pmdp_get(pmd)); > P4D doesn't exist on LoongArch, PUD huge page isn't supported on > LoongArch, so what are you doing? my mistake. p4d is folded here and there is no PUD huge page on LoongArch, so p4d_leaf()/pud_leaf() are always false in this path -- they were unnecessary. I'll drop both in v2 and keep only the pmd_leaf()/pte paths. Thanks, Song > Huacai > >> } else { >> pte = pte_offset_kernel(pmd, address); >> if (!pte_present(ptep_get(pte))) >> return 0; >> - >> - return write ? pte_write(ptep_get(pte)) : 1; >> + prot = pte_val(ptep_get(pte)); >> } >> + >> + /* >> + * The TLB-protect handler passes write=0 for read (TLBNR), exec (TLBNX) >> + * and privilege (TLBPE) faults alike, so recover the real cause from the >> + * exception code; otherwise an exec into a present NX page (or a read of >> + * a NO_READ page) is wrongly treated as spurious and loops forever. >> + */ >> + if (write) >> + return prot & _PAGE_WRITE; >> + if (read_csr_excode() == EXCCODE_TLBNX) >> + return !(prot & _PAGE_NO_EXEC); >> + return !(prot & _PAGE_NO_READ); >> } >> >> static void __kprobes no_context(struct pt_regs *regs, >> -- >> 2.43.0 >>