x86/entry_32: Fix segment exceptions
authorPeter Zijlstra <peterz@infradead.org>
Tue, 11 Jan 2022 11:11:14 +0000 (12:11 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 29 Jul 2022 15:25:33 +0000 (17:25 +0200)
commit 9cdbeec4096804083944d05da96bbaf59a1eb4f9 upstream.

The LKP robot reported that commit in Fixes: caused a failure. Turns out
the ldt_gdt_32 selftest turns into an infinite loop trying to clear the
segment.

As discovered by Sean, what happens is that PARANOID_EXIT_TO_KERNEL_MODE
in the handle_exception_return path overwrites the entry stack data with
the task stack data, restoring the "bad" segment value.

Instead of having the exception retry the instruction, have it emulate
the full instruction. Replace EX_TYPE_POP_ZERO with EX_TYPE_POP_REG
which will do the equivalent of: POP %reg; MOV $imm, %reg.

In order to encode the segment registers, add them as registers 8-11 for
32-bit.

By setting regs->[defg]s the (nested) RESTORE_REGS will pop this value
at the end of the exception handler and by increasing regs->sp, it will
have skipped the stack slot.

This was debugged by Sean Christopherson <seanjc@google.com>.

 [ bp: Add EX_REG_GS too. ]

Fixes: aa93e2ad7464 ("x86/entry_32: Remove .fixup usage")
Reported-by: kernel test robot <oliver.sang@intel.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Signed-off-by: Borislav Petkov <bp@suse.de>
Link: https://lore.kernel.org/r/Yd1l0gInc4zRcnt/@hirez.programming.kicks-ass.net
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
arch/x86/entry/entry_32.S
arch/x86/include/asm/extable_fixup_types.h
arch/x86/lib/insn-eval.c
arch/x86/mm/extable.c

index 2cba70f9753bc0918161ea56d563bfd3df3c0708..6b44263d7efbc5b9be9c2a3a5a482df61ae261d7 100644 (file)
 1:     popl    %ds
 2:     popl    %es
 3:     popl    %fs
-       addl    $(4 + \pop), %esp       /* pop the unused "gs" slot */
+4:     addl    $(4 + \pop), %esp       /* pop the unused "gs" slot */
        IRET_FRAME
-       _ASM_EXTABLE_TYPE(1b, 1b, EX_TYPE_POP_ZERO)
-       _ASM_EXTABLE_TYPE(2b, 2b, EX_TYPE_POP_ZERO)
-       _ASM_EXTABLE_TYPE(3b, 3b, EX_TYPE_POP_ZERO)
+
+       /*
+        * There is no _ASM_EXTABLE_TYPE_REG() for ASM, however since this is
+        * ASM the registers are known and we can trivially hard-code them.
+        */
+       _ASM_EXTABLE_TYPE(1b, 2b, EX_TYPE_POP_ZERO|EX_REG_DS)
+       _ASM_EXTABLE_TYPE(2b, 3b, EX_TYPE_POP_ZERO|EX_REG_ES)
+       _ASM_EXTABLE_TYPE(3b, 4b, EX_TYPE_POP_ZERO|EX_REG_FS)
 .endm
 
 .macro RESTORE_ALL_NMI cr3_reg:req pop=0
index 7469038de100dc8bc62fec92ae44a715d58339c0..b3b785b9bb14c4e63e0eeade652fe52cbd12c514 100644 (file)
 #define EX_DATA_FLAG_SHIFT             12
 #define EX_DATA_IMM_SHIFT              16
 
+#define EX_DATA_REG(reg)               ((reg) << EX_DATA_REG_SHIFT)
 #define EX_DATA_FLAG(flag)             ((flag) << EX_DATA_FLAG_SHIFT)
 #define EX_DATA_IMM(imm)               ((imm) << EX_DATA_IMM_SHIFT)
 
+/* segment regs */
+#define EX_REG_DS                      EX_DATA_REG(8)
+#define EX_REG_ES                      EX_DATA_REG(9)
+#define EX_REG_FS                      EX_DATA_REG(10)
+#define EX_REG_GS                      EX_DATA_REG(11)
+
 /* flags */
 #define EX_FLAG_CLEAR_AX               EX_DATA_FLAG(1)
 #define EX_FLAG_CLEAR_DX               EX_DATA_FLAG(2)
@@ -41,7 +48,9 @@
 #define        EX_TYPE_RDMSR_IN_MCE            13
 #define        EX_TYPE_DEFAULT_MCE_SAFE        14
 #define        EX_TYPE_FAULT_MCE_SAFE          15
-#define        EX_TYPE_POP_ZERO                16
+
+#define        EX_TYPE_POP_REG                 16 /* sp += sizeof(long) */
+#define EX_TYPE_POP_ZERO               (EX_TYPE_POP_REG | EX_DATA_IMM(0))
 
 #define        EX_TYPE_IMM_REG                 17 /* reg := (long)imm */
 #define        EX_TYPE_EFAULT_REG              (EX_TYPE_IMM_REG | EX_DATA_IMM(-EFAULT))
index 7760d228041bac7567e220db7e43162a14fd76a9..c8a962c2e653d1e8f5dea37e52aea0efae65c7b6 100644 (file)
@@ -430,6 +430,11 @@ static const int pt_regoff[] = {
        offsetof(struct pt_regs, r13),
        offsetof(struct pt_regs, r14),
        offsetof(struct pt_regs, r15),
+#else
+       offsetof(struct pt_regs, ds),
+       offsetof(struct pt_regs, es),
+       offsetof(struct pt_regs, fs),
+       offsetof(struct pt_regs, gs),
 #endif
 };
 
index 1c558f8e8c079b6a873f55228c7e24bb1bb275a9..13d838e6030bc1bfa80ec861cf9beed24567aced 100644 (file)
@@ -118,18 +118,6 @@ static bool ex_handler_clear_fs(const struct exception_table_entry *fixup,
        return ex_handler_default(fixup, regs);
 }
 
-static bool ex_handler_pop_zero(const struct exception_table_entry *fixup,
-                               struct pt_regs *regs)
-{
-       /*
-        * Typically used for when "pop %seg" traps, in which case we'll clear
-        * the stack slot and re-try the instruction, which will then succeed
-        * to pop zero.
-        */
-       *((unsigned long *)regs->sp) = 0;
-       return ex_handler_default(fixup, regs);
-}
-
 static bool ex_handler_imm_reg(const struct exception_table_entry *fixup,
                               struct pt_regs *regs, int reg, int imm)
 {
@@ -203,8 +191,9 @@ int fixup_exception(struct pt_regs *regs, int trapnr, unsigned long error_code,
        case EX_TYPE_RDMSR_IN_MCE:
                ex_handler_msr_mce(regs, false);
                break;
-       case EX_TYPE_POP_ZERO:
-               return ex_handler_pop_zero(e, regs);
+       case EX_TYPE_POP_REG:
+               regs->sp += sizeof(long);
+               fallthrough;
        case EX_TYPE_IMM_REG:
                return ex_handler_imm_reg(e, regs, reg, imm);
        }