From: Josh Poimboeuf <jpoimboe@redhat.com>
To: x86@kernel.org
Cc: linux-kernel@vger.kernel.org,
Peter Zijlstra <peterz@infradead.org>,
Vince Weaver <vincent.weaver@maine.edu>, Dave Jones <dsj@fb.com>,
Jann Horn <jannh@google.com>, Miroslav Benes <mbenes@suse.cz>,
Andy Lutomirski <luto@kernel.org>,
Steven Rostedt <rostedt@goodmis.org>,
Thomas Gleixner <tglx@linutronix.de>
Subject: [PATCH 12/14] x86/unwind/orc: Fix premature unwind stoppage due to IRET frames
Date: Thu, 12 Mar 2020 12:30:31 -0500 [thread overview]
Message-ID: <ae767ea82e29f2b9f89b411b7b7bf2ea77ffd6ae.1584033751.git.jpoimboe@redhat.com> (raw)
In-Reply-To: <cover.1584033751.git.jpoimboe@redhat.com>
The following execution path is possible:
fsnotify()
[ realign the stack and store previous SP in R10 ]
<IRQ>
[ only IRET regs saved ]
common_interrupt()
interrupt_entry()
<NMI>
[ full pt_regs saved ]
...
[ unwind stack ]
When the unwinder goes through the NMI and the IRQ on the stack, and
then sees fsnotify(), it doesn't have access to the value of R10,
because it only has the five IRET registers. So the unwind stops
prematurely.
However, because the interrupt_entry() code is careful not to clobber
R10 before saving the full regs, the unwinder should be able to read R10
from the previously saved full pt_regs associated with the NMI.
Handle this case properly. When encountering an IRET regs frame
immediately after a full pt_regs frame, use the pt_regs as a backup
which can be used to get the C register values.
Also, note that a call frame resets the 'prev_regs' value, because a
function is free to clobber the registers. For this fix to work, the
IRET and full regs frames must be adjacent, with no FUNC frames in
between. So replace the FUNC hint in interrupt_entry() with an
IRET_REGS hint.
Fixes: ee9f8fce9964 ("x86/unwind: Add the ORC unwinder")
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
---
arch/x86/entry/entry_64.S | 4 +--
arch/x86/include/asm/unwind.h | 2 +-
arch/x86/kernel/unwind_orc.c | 51 +++++++++++++++++++++++++++--------
3 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/arch/x86/entry/entry_64.S b/arch/x86/entry/entry_64.S
index c1eaeab0e0d4..faa53fee0663 100644
--- a/arch/x86/entry/entry_64.S
+++ b/arch/x86/entry/entry_64.S
@@ -511,7 +511,7 @@ SYM_CODE_END(spurious_entries_start)
* +----------------------------------------------------+
*/
SYM_CODE_START(interrupt_entry)
- UNWIND_HINT_FUNC
+ UNWIND_HINT_IRET_REGS offset=16
ASM_CLAC
cld
@@ -543,9 +543,9 @@ SYM_CODE_START(interrupt_entry)
pushq 5*8(%rdi) /* regs->eflags */
pushq 4*8(%rdi) /* regs->cs */
pushq 3*8(%rdi) /* regs->ip */
+ UNWIND_HINT_IRET_REGS
pushq 2*8(%rdi) /* regs->orig_ax */
pushq 8(%rdi) /* return address */
- UNWIND_HINT_FUNC
movq (%rdi), %rdi
jmp 2f
diff --git a/arch/x86/include/asm/unwind.h b/arch/x86/include/asm/unwind.h
index 499578f7e6d7..70fc159ebe69 100644
--- a/arch/x86/include/asm/unwind.h
+++ b/arch/x86/include/asm/unwind.h
@@ -19,7 +19,7 @@ struct unwind_state {
#if defined(CONFIG_UNWINDER_ORC)
bool signal, full_regs;
unsigned long sp, bp, ip;
- struct pt_regs *regs;
+ struct pt_regs *regs, *prev_regs;
#elif defined(CONFIG_UNWINDER_FRAME_POINTER)
bool got_irq;
unsigned long *bp, *orig_sp, ip;
diff --git a/arch/x86/kernel/unwind_orc.c b/arch/x86/kernel/unwind_orc.c
index 33b80a7f998f..0ebc11a8bb45 100644
--- a/arch/x86/kernel/unwind_orc.c
+++ b/arch/x86/kernel/unwind_orc.c
@@ -384,9 +384,38 @@ static bool deref_stack_iret_regs(struct unwind_state *state, unsigned long addr
return true;
}
+/*
+ * If state->regs is non-NULL, and points to a full pt_regs, just get the reg
+ * value from state->regs.
+ *
+ * Otherwise, if state->regs just points to IRET regs, and the previous frame
+ * had full regs, it's safe to get the value from the previous regs. This can
+ * happen when early/late IRQ entry code gets interrupted by an NMI.
+ */
+static bool get_reg(struct unwind_state *state, unsigned int reg_off,
+ unsigned long *val)
+{
+ unsigned int reg = reg_off/8;
+
+ if (!state->regs)
+ return false;
+
+ if (state->full_regs) {
+ *val = ((unsigned long *)state->regs)[reg];
+ return true;
+ }
+
+ if (state->prev_regs) {
+ *val = ((unsigned long *)state->prev_regs)[reg];
+ return true;
+ }
+
+ return false;
+}
+
bool unwind_next_frame(struct unwind_state *state)
{
- unsigned long ip_p, sp, orig_ip = state->ip, prev_sp = state->sp;
+ unsigned long ip_p, sp, tmp, orig_ip = state->ip, prev_sp = state->sp;
enum stack_type prev_type = state->stack_info.type;
struct orc_entry *orc;
bool indirect = false;
@@ -448,39 +477,35 @@ bool unwind_next_frame(struct unwind_state *state)
break;
case ORC_REG_R10:
- if (!state->regs || !state->full_regs) {
+ if (!get_reg(state, offsetof(struct pt_regs, r10), &sp)) {
orc_warn_current("missing R10 value at %pB\n",
(void *)state->ip);
goto err;
}
- sp = state->regs->r10;
break;
case ORC_REG_R13:
- if (!state->regs || !state->full_regs) {
+ if (!get_reg(state, offsetof(struct pt_regs, r13), &sp)) {
orc_warn_current("missing R13 value at %pB\n",
(void *)state->ip);
goto err;
}
- sp = state->regs->r13;
break;
case ORC_REG_DI:
- if (!state->regs || !state->full_regs) {
+ if (!get_reg(state, offsetof(struct pt_regs, di), &sp)) {
orc_warn_current("missing RDI value at %pB\n",
(void *)state->ip);
goto err;
}
- sp = state->regs->di;
break;
case ORC_REG_DX:
- if (!state->regs || !state->full_regs) {
+ if (!get_reg(state, offsetof(struct pt_regs, dx), &sp)) {
orc_warn_current("missing DX value at %pB\n",
(void *)state->ip);
goto err;
}
- sp = state->regs->dx;
break;
default:
@@ -507,6 +532,7 @@ bool unwind_next_frame(struct unwind_state *state)
state->sp = sp;
state->regs = NULL;
+ state->prev_regs = NULL;
state->signal = false;
break;
@@ -518,6 +544,7 @@ bool unwind_next_frame(struct unwind_state *state)
}
state->regs = (struct pt_regs *)sp;
+ state->prev_regs = NULL;
state->full_regs = true;
state->signal = true;
break;
@@ -529,6 +556,8 @@ bool unwind_next_frame(struct unwind_state *state)
goto err;
}
+ if (state->full_regs)
+ state->prev_regs = state->regs;
state->regs = (void *)sp - IRET_FRAME_OFFSET;
state->full_regs = false;
state->signal = true;
@@ -543,8 +572,8 @@ bool unwind_next_frame(struct unwind_state *state)
/* Find BP: */
switch (orc->bp_reg) {
case ORC_REG_UNDEFINED:
- if (state->regs && state->full_regs)
- state->bp = state->regs->bp;
+ if (get_reg(state, offsetof(struct pt_regs, bp), &tmp))
+ state->bp = tmp;
break;
case ORC_REG_PREV_SP:
--
2.21.1
next prev parent reply other threads:[~2020-03-12 17:31 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-03-12 17:30 [PATCH 00/14] x86/unwind/orc: ORC fixes Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 01/14] x86/dumpstack: Add SHOW_REGS_IRET mode Josh Poimboeuf
2020-03-13 11:10 ` Miroslav Benes
2020-03-12 17:30 ` [PATCH 02/14] objtool: Fix stack offset tracking for indirect CFAs Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 03/14] x86/entry/64: Fix unwind hints in register clearing code Josh Poimboeuf
2020-03-12 19:29 ` Andy Lutomirski
2020-03-12 19:57 ` Josh Poimboeuf
2020-03-12 20:07 ` Peter Zijlstra
2020-03-12 21:24 ` Jann Horn
2020-03-12 17:30 ` [PATCH 04/14] x86/entry/64: Fix unwind hints in kernel exit path Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 05/14] x86/entry/64: Fix unwind hints in __switch_to_asm() Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 06/14] x86/entry/64: Fix unwind hints in rewind_stack_do_exit() Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 07/14] x86/unwind/orc: Convert global variables to static Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 08/14] x86/unwind: Prevent false warnings for non-current tasks Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 09/14] x86/unwind/orc: Don't skip the first frame for inactive tasks Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 10/14] x86/unwind/orc: Prevent unwinding before ORC initialization Josh Poimboeuf
2020-03-13 12:24 ` Miroslav Benes
2020-03-12 17:30 ` [PATCH 11/14] x86/unwind/orc: Fix error path for bad ORC entry type Josh Poimboeuf
2020-03-12 17:30 ` Josh Poimboeuf [this message]
2020-03-12 17:30 ` [PATCH 13/14] x86/unwind/orc: Add more unwinder warnings Josh Poimboeuf
2020-03-12 19:12 ` Jann Horn
2020-03-12 19:20 ` Josh Poimboeuf
2020-03-12 17:30 ` [PATCH 14/14] x86/unwind/orc: Add 'unwind_debug' cmdline option Josh Poimboeuf
2020-03-12 19:15 ` [PATCH 00/14] x86/unwind/orc: ORC fixes Peter Zijlstra
2020-03-13 14:00 ` Miroslav Benes
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ae767ea82e29f2b9f89b411b7b7bf2ea77ffd6ae.1584033751.git.jpoimboe@redhat.com \
--to=jpoimboe@redhat.com \
--cc=dsj@fb.com \
--cc=jannh@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=luto@kernel.org \
--cc=mbenes@suse.cz \
--cc=peterz@infradead.org \
--cc=rostedt@goodmis.org \
--cc=tglx@linutronix.de \
--cc=vincent.weaver@maine.edu \
--cc=x86@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®