NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
port-mips/60664: mips: rdhwr emulation corrupts v0 on nested TLB miss, random userland SIGSEGV
>Number: 60664
>Category: port-mips
>Synopsis: mips: rdhwr emulation corrupts v0 on nested TLB miss, random userland SIGSEGV
>Confidential: no
>Severity: serious
>Priority: high
>Responsible: port-mips-maintainer
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Fri Aug 28 23:50:01 +0000 2026
>Originator: Imre Kaloz
>Release: NetBSD 11.0 (also -current)
>Organization:
>Environment:
NetBSD indigo 11.0 NetBSD 11.0 (GENERIC32_IP2x) #0: Thu Jul 30 15:23:12 UTC 2026
mkrepro%mkrepro.NetBSD.org@localhost:/usr/src/sys/arch/sgimips/compile/GENERIC32_IP2x sgimips
>Description:
On CPUs without RDHWR every TLS access traps into MIPSX(user_reserved_insn), which re-reads the trapping instruction from user space with a plain inline load while EXL is still set. If that read takes a TLB miss (the entry can be evicted by a refill for the mapped kernel stack moments earlier), the exception dispatch, which only looks at KSU, treats it as a fresh trap from userland and clobbers the v0 save slot the routine is still using. The emulation then returns the trap's own exception PC to the program as its TLS pointer, and the process later crashes at some unrelated address. From the outside it looks like random memory corruption, took a while to pin down.
>How-To-Repeat:
Run a few concurrent loops of dynamically linked commands (while :; do /bin/ls >/dev/null; done) next to a memory hog bigger than RAM, so paging keeps the TLB busy. I get a SIGSEGV rate of about 1 in 7.
>Fix:
Check the page with TLBP before the inline read and fall back to the existing protected slow path if it is not mapped.
Signed-off-by: Imre Kaloz <kaloz%dune.hu@localhost>
--- a/sys/arch/mips/mips/mipsX_subr.S
+++ b/sys/arch/mips/mips/mipsX_subr.S
@@ -1320,11 +1320,43 @@ NESTED_NOPROFILE(MIPSX(user_reserved_insn), CALLFRAME_SIZ, ra)
nop
/*
- * Get exception PC and fetch the instruction. We know we can do
- * this since the instruction actually got read.
+ * Get exception PC and fetch the instruction.
+ *
+ * Probe the page with TLBP first, and take the slow path if the
+ * entry isn't there. A nested miss on the inline read below would
+ * otherwise land in user_gen_exception and clobber this routine's
+ * own v0, since the exception PC isn't saved again while EXL is set.
*/
_MFC0 v0, MIPS_COP_0_EXC_PC
MFC0_HAZARD
+
+ /* Need v1 and a0 too */
+ REG_S v1, CALLFRAME_SIZ+TF_REG_V1(k0)
+ REG_S a0, CALLFRAME_SIZ+TF_REG_A0(k0)
+ _MFC0 v1, MIPS_COP_0_TLB_HI # save current ASID
+ MFC0_HAZARD
+ andi a0, v1, MIPS3_PG_ASID
+ move AT, v0
+#ifdef MIPSNNR2
+ _INS AT, a0, 0, V_MIPS3_PG_HVPN
+#else
+ _SRA AT, V_MIPS3_PG_HVPN # clear bottom bits of VA
+ _SLL AT, V_MIPS3_PG_HVPN # clear bottom bits of VA
+ or AT, AT, a0
+#endif
+ _MTC0 AT, MIPS_COP_0_TLB_HI # look for the vaddr & ASID
+ COP0_SYNC
+ tlbp # probe the entry in question
+ COP0_SYNC
+ mfc0 AT, MIPS_COP_0_TLB_INDEX # see what we got
+ MFC0_HAZARD
+ _MTC0 v1, MIPS_COP_0_TLB_HI # restore ASID
+ COP0_SYNC
+ /* Restore v1 and a0 for either outcome */
+ REG_L v1, CALLFRAME_SIZ+TF_REG_V1(k0)
+ bltz AT, MIPSX(user_gen_exception_common) # index < 0 => !found
+ REG_L a0, CALLFRAME_SIZ+TF_REG_A0(k0)
+
INT_L AT, 0(v0)
/*
Home |
Main Index |
Thread Index |
Old Index