Re: [PATCH] LoongArch: fix silent hang on kernel exec into an NX page
From: Huacai Chen
Date: Thu Jul 16 2026 - 02:24:58 EST
Hi, Song,
On Thu, Jul 16, 2026 at 11:02 AM Song Hu <husong@xxxxxxxxxx> 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?
>
> 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 <husong@xxxxxxxxxx>
> ---
> 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?
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
>