Fwd: [PATCH] x86/nmi/64: avoid passing user space rsp of pt_regs to nmi handler

From: oliver yang
Date: Tue Aug 22 2017 - 13:39:54 EST


Copied linux-kernel, as my email got rejected by the alias.


---------- Forwarded message ----------
From: yang oliver <yang_oliver@xxxxxxxxxxx>
Date: 2017-08-23 1:09 GMT+08:00
Subject: [PATCH] x86/nmi/64: avoid passing user space rsp of pt_regs
to nmi handler
To: "tglx@xxxxxxxxxxxxx" <tglx@xxxxxxxxxxxxx>, "mingo@xxxxxxxxxx"
<mingo@xxxxxxxxxx>, "hpa@xxxxxxxxx" <hpa@xxxxxxxxx>, "luto@xxxxxxxxxx"
<luto@xxxxxxxxxx>, "x86@xxxxxxxxxx" <x86@xxxxxxxxxx>
æéï "rostedt@xxxxxxxxxxx" <rostedt@xxxxxxxxxxx>, "jpoimboe@xxxxxxxxxx"
<jpoimboe@xxxxxxxxxx>, "linux-kernel@xxxxxxxxxxxxxxx"
<linux-kernel@xxxxxxxxxxxxxxx>, Yong Yang <yangoliver@xxxxxxxxx>


From: Yong Yang <yangoliver@xxxxxxxxx>

While NMI interrupts the very beginning of SYSCALL, the rsp could
be still user space address without loading kernel address. Then
the pt_regs constructed by the NMI entry point could have a user
space rsp. If a NMI handler tried to dump stack by using this rsp,
it can cause the kernel panic.

This patch tries to check the rsp saved in outermost frame, and
will do another copy for pt_regs. If the rsp of outermost frame
is from user space, then use the kernel address of the rsp as the
member of pt_regs.

Signed-off-by: Yong Yang <yangoliver@xxxxxxxxx>
---
arch/x86/entry/entry_64.S | 37 +++++++++++++++++++++++++++++--------
1 file changed, 29 insertions(+), 8 deletions(-)

diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index d271fb7..dc85452 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -1298,7 +1298,13 @@ ENTRY(nmi)
* | outermost CS } Copied to "iret" frame on each |
* | outermost RIP } iteration. |
* +---------------------------------------------------------+
- * | pt_regs |
+ * | pt_regs SS } initialized in first_nmi; |
+ * | pt_regs Return RSP } will not be changed before |
+ * | pt_regs RFLAGS } NMI processing is done. |
+ * | pt_regs CS } Passed to do_nmi for NMI handler. |
+ * | pt_regs RIP } User space RSP could be replaced. |
+ * +---------------------------------------------------------+
+ * | pt_regs (remaining registers) |
* +---------------------------------------------------------+
*
* The "original" frame is used by hardware. Before re-enabling
@@ -1394,9 +1400,6 @@ nested_nmi_out:
INTERRUPT_RETURN

first_nmi:
- /* Restore rdx. */
- movq (%rsp), %rdx
-
/* Make room for "NMI executing". */
pushq $0

@@ -1408,6 +1411,24 @@ first_nmi:
pushq 11*8(%rsp)
.endr

+ /* Copy the "outermost" frame to pt_regs frame */
+ pushq 16*8(%rsp)
+ movq $__PAGE_OFFSET, %rdx
+ cmpq 16*8(%rsp), %rdx /* kernel rsp? */
+ jbe 2f
+ /* Save user RSP to avoid pt_regs->rsp got referenced by NMI handler */
+ pushq %rsp
+ jmp 3f
+2:
+ pushq 16*8(%rsp)
+3:
+ .rept 3
+ pushq 16*8(%rsp)
+ .endr
+
+ /* Restore rdx. */
+ movq 16*8(%rsp), %rdx
+
/* Everything up to here is safe from nested NMIs */

#ifdef CONFIG_DEBUG_ENTRY
@@ -1441,18 +1462,18 @@ repeat_nmi:
* gsbase if needed before we call do_nmi. "NMI executing"
* is zero.
*/
- movq $1, 10*8(%rsp) /* Set "NMI executing". */
+ movq $1, 15*8(%rsp) /* Set "NMI executing". */

/*
* Copy the "outermost" frame to the "iret" frame. NMIs that nest
* here must not modify the "iret" frame while we're writing to
* it or it will end up containing garbage.
*/
- addq $(10*8), %rsp
+ addq $(15*8), %rsp
.rept 5
pushq -6*8(%rsp)
.endr
- subq $(5*8), %rsp
+ subq $(10*8), %rsp
end_repeat_nmi:

/*
@@ -1486,7 +1507,7 @@ nmi_restore:
RESTORE_C_REGS

/* Point RSP at the "iret" frame. */
- REMOVE_PT_GPREGS_FROM_STACK 6*8
+ REMOVE_PT_GPREGS_FROM_STACK 11*8

/*
* Clear "NMI executing". Set DF first so that we can easily
--
1.8.3.1



--
------------------
Oliver Yang