LoongArch: Relay BCE exceptions to userland as SIGSEGV with si_code=SEGV_BNDERR
authorWANG Xuerui <git@xen0n.name>
Mon, 1 May 2023 09:19:27 +0000 (17:19 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Mon, 1 May 2023 09:19:27 +0000 (17:19 +0800)
SEGV_BNDERR was introduced initially for supporting the Intel MPX, but
fell into disuse after the MPX support was removed. The LoongArch
bounds-checking instructions behave very differently than MPX, but
overall the interface is still kind of suitable for conveying the
information to userland when bounds-checking assertions trigger, so we
wouldn't have to invent more UAPI. Specifically, when the BCE triggers,
a SEGV_BNDERR is sent to userland, with si_addr set to the out-of-bounds
address or value (in asrt{gt,le}'s case), and one of si_lower or
si_upper set to the configured bound depending on the faulting
instruction. The other bound is set to either 0 or ULONG_MAX to resemble
a range with both lower and upper bounds.

Note that it is possible to have si_addr == si_lower in case of a
failing asrtgt or {ld,st}gt, because those instructions test for strict
greater-than relationship. This should not pose a problem for userland,
though, because the faulting PC is available for the application to
associate back to the exact instruction for figuring out the
expectation.

Example exception context generated by a faulting `asrtgt.d t0, t1`
(assert t0 > t1 or BCE) with t0=100 and t1=200:

> pc 00005555558206a4 ra 00007ffff2d854fc tp 00007ffff2f2f180 sp 00007ffffbf9fb80
> a0 0000000000000002 a1 00007ffffbf9fce8 a2 00007ffffbf9fd00 a3 00007ffff2ed4558
> a4 0000000000000000 a5 00007ffff2f044c8 a6 00007ffffbf9fce0 a7 fffffffffffff000
> t0 0000000000000064 t1 00000000000000c8 t2 00007ffffbfa2d5e t3 00007ffff2f12aa0
> t4 00007ffff2ed6158 t5 00007ffff2ed6158 t6 000000000000002e t7 0000000003d8f538
> t8 0000000000000005 u0 0000000000000000 s9 0000000000000000 s0 00007ffffbf9fce8
> s1 0000000000000002 s2 0000000000000000 s3 00007ffff2f2c038 s4 0000555555820610
> s5 00007ffff2ed5000 s6 0000555555827e38 s7 00007ffffbf9fd00 s8 0000555555827e38
>    ra: 00007ffff2d854fc
>   ERA: 00005555558206a4
>  CRMD: 000000b0 (PLV0 -IE -DA +PG DACF=CC DACM=CC -WE)
>  PRMD: 00000007 (PPLV3 +PIE -PWE)
>  EUEN: 00000000 (-FPE -SXE -ASXE -BTE)
>  ECFG: 0007181c (LIE=2-4,11-12 VS=7)
> ESTAT: 000a0000 [BCE] (IS= ECode=10 EsubCode=0)
>  PRID: 0014c010 (Loongson-64bit, Loongson-3A5000)

Signed-off-by: WANG Xuerui <git@xen0n.name>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/include/asm/inst.h
arch/loongarch/kernel/genex.S
arch/loongarch/kernel/traps.c

index a04fe75..b09887f 100644 (file)
@@ -121,6 +121,8 @@ enum reg2bstrd_op {
 };
 
 enum reg3_op {
+       asrtle_op       = 0x02,
+       asrtgt_op       = 0x03,
        addw_op         = 0x20,
        addd_op         = 0x21,
        subw_op         = 0x22,
@@ -176,6 +178,30 @@ enum reg3_op {
        amord_op        = 0x70c7,
        amxorw_op       = 0x70c8,
        amxord_op       = 0x70c9,
+       fldgts_op       = 0x70e8,
+       fldgtd_op       = 0x70e9,
+       fldles_op       = 0x70ea,
+       fldled_op       = 0x70eb,
+       fstgts_op       = 0x70ec,
+       fstgtd_op       = 0x70ed,
+       fstles_op       = 0x70ee,
+       fstled_op       = 0x70ef,
+       ldgtb_op        = 0x70f0,
+       ldgth_op        = 0x70f1,
+       ldgtw_op        = 0x70f2,
+       ldgtd_op        = 0x70f3,
+       ldleb_op        = 0x70f4,
+       ldleh_op        = 0x70f5,
+       ldlew_op        = 0x70f6,
+       ldled_op        = 0x70f7,
+       stgtb_op        = 0x70f8,
+       stgth_op        = 0x70f9,
+       stgtw_op        = 0x70fa,
+       stgtd_op        = 0x70fb,
+       stleb_op        = 0x70fc,
+       stleh_op        = 0x70fd,
+       stlew_op        = 0x70fe,
+       stled_op        = 0x70ff,
 };
 
 enum reg3sa2_op {
index 44ff1ff..78f0663 100644 (file)
@@ -82,6 +82,7 @@ SYM_FUNC_END(except_vec_cex)
 
        BUILD_HANDLER ade ade badv
        BUILD_HANDLER ale ale badv
+       BUILD_HANDLER bce bce none
        BUILD_HANDLER bp bp none
        BUILD_HANDLER fpe fpe fcsr
        BUILD_HANDLER fpu fpu none
index 569c3a5..8db26e4 100644 (file)
@@ -36,6 +36,7 @@
 #include <asm/break.h>
 #include <asm/cpu.h>
 #include <asm/fpu.h>
+#include <asm/inst.h>
 #include <asm/loongarch.h>
 #include <asm/mmu_context.h>
 #include <asm/pgtable.h>
@@ -51,6 +52,7 @@
 
 extern asmlinkage void handle_ade(void);
 extern asmlinkage void handle_ale(void);
+extern asmlinkage void handle_bce(void);
 extern asmlinkage void handle_sys(void);
 extern asmlinkage void handle_bp(void);
 extern asmlinkage void handle_ri(void);
@@ -588,6 +590,95 @@ static void bug_handler(struct pt_regs *regs)
        }
 }
 
+asmlinkage void noinstr do_bce(struct pt_regs *regs)
+{
+       bool user = user_mode(regs);
+       unsigned long era = exception_era(regs);
+       u64 badv = 0, lower = 0, upper = ULONG_MAX;
+       union loongarch_instruction insn;
+       irqentry_state_t state = irqentry_enter(regs);
+
+       if (regs->csr_prmd & CSR_PRMD_PIE)
+               local_irq_enable();
+
+       current->thread.trap_nr = read_csr_excode();
+
+       die_if_kernel("Bounds check error in kernel code", regs);
+
+       /*
+        * Pull out the address that failed bounds checking, and the lower /
+        * upper bound, by minimally looking at the faulting instruction word
+        * and reading from the correct register.
+        */
+       if (__get_inst(&insn.word, (u32 *)era, user))
+               goto bad_era;
+
+       switch (insn.reg3_format.opcode) {
+       case asrtle_op:
+               if (insn.reg3_format.rd != 0)
+                       break;  /* not asrtle */
+               badv = regs->regs[insn.reg3_format.rj];
+               upper = regs->regs[insn.reg3_format.rk];
+               break;
+
+       case asrtgt_op:
+               if (insn.reg3_format.rd != 0)
+                       break;  /* not asrtgt */
+               badv = regs->regs[insn.reg3_format.rj];
+               lower = regs->regs[insn.reg3_format.rk];
+               break;
+
+       case ldleb_op:
+       case ldleh_op:
+       case ldlew_op:
+       case ldled_op:
+       case stleb_op:
+       case stleh_op:
+       case stlew_op:
+       case stled_op:
+       case fldles_op:
+       case fldled_op:
+       case fstles_op:
+       case fstled_op:
+               badv = regs->regs[insn.reg3_format.rj];
+               upper = regs->regs[insn.reg3_format.rk];
+               break;
+
+       case ldgtb_op:
+       case ldgth_op:
+       case ldgtw_op:
+       case ldgtd_op:
+       case stgtb_op:
+       case stgth_op:
+       case stgtw_op:
+       case stgtd_op:
+       case fldgts_op:
+       case fldgtd_op:
+       case fstgts_op:
+       case fstgtd_op:
+               badv = regs->regs[insn.reg3_format.rj];
+               lower = regs->regs[insn.reg3_format.rk];
+               break;
+       }
+
+       force_sig_bnderr((void __user *)badv, (void __user *)lower, (void __user *)upper);
+
+out:
+       if (regs->csr_prmd & CSR_PRMD_PIE)
+               local_irq_disable();
+
+       irqentry_exit(regs, state);
+       return;
+
+bad_era:
+       /*
+        * Cannot pull out the instruction word, hence cannot provide more
+        * info than a regular SIGSEGV in this case.
+        */
+       force_sig(SIGSEGV);
+       goto out;
+}
+
 asmlinkage void noinstr do_bp(struct pt_regs *regs)
 {
        bool user = user_mode(regs);
@@ -955,6 +1046,7 @@ void __init trap_init(void)
 
        set_handler(EXCCODE_ADE * VECSIZE, handle_ade, VECSIZE);
        set_handler(EXCCODE_ALE * VECSIZE, handle_ale, VECSIZE);
+       set_handler(EXCCODE_BCE * VECSIZE, handle_bce, VECSIZE);
        set_handler(EXCCODE_SYS * VECSIZE, handle_sys, VECSIZE);
        set_handler(EXCCODE_BP * VECSIZE, handle_bp, VECSIZE);
        set_handler(EXCCODE_INE * VECSIZE, handle_ri, VECSIZE);