Skip to contentSkip to conversation

kprobes/x86: Fix to clear TF bit in fault-on-single-stepping

PATCH In review Masami Hiramatsu 5 participants 7 messages Jun 11, 2016 – Jun 14, 2016 x86

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 kprobes/x86: Fix to clear TF bit in fault-on-single-stepping

Fix kprobe_fault_handler to clear TF (trap flag) bit of flags register in the case of fault fixup on single-stepping.

If we put a kprobe on the instruction which can cause a page fault (e.g. actual mov instructions in copy_user_*), that fault happens on a single-stepping buffer. In this case, kprobes resets running instance so that the CPU can retry execution on the original ip address. However, current code forgets reset TF bit. Since this fault happens with TF bit set for enabling single-stepping, when it retries, it causes a debug exception and kprobes can not handle it because it already reset itself.

On the most of x86-64 platform, it can be easily reproduced by using kprobe tracer. E.g.

  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

---
 arch/x86/kernel/kprobes/core.c |    7 +++++++
 1 file changed, 7 insertions(+)
arch/x86/kernel/kprobes/core.c +7 -0
index 38cf7a7..856df81 100644
@@ -961,6 +961,13 @@ int kprobe_fault_handler(struct pt_regs *regs, int trapnr)
 		 * normal page fault.
 		 */
 		regs->ip = (unsigned long)cur->addr;
+		/*
+		 * Trap flag has been set here because this fault happened
+		 * where the single stepping will be done. So clear it with
+		 * resetting current kprobe.
+		 */
+		regs->flags &= ~X86_EFLAGS_TF;
+		/* If the TF was set before the kprobe hit, don't touch it */
 		regs->flags |= kcb->kprobe_old_flags;
 		if (kcb->kprobe_status == KPROBE_REENTER)
 			restore_previous_kprobe(kcb);
Reviewed-by on v1 1/1
#1 lore raw
Quoted 20 lines · Masami Hiramatsu

Fix kprobe_fault_handler to clear TF (trap flag) bit of flags register in the case of fault fixup on single-stepping.

If we put a kprobe on the instruction which can cause a page fault (e.g. actual mov instructions in copy_user_*), that fault happens on a single-stepping buffer. In this case, kprobes resets running instance so that the CPU can retry execution on the original ip address. However, current code forgets reset TF bit. Since this fault happens with TF bit set for enabling single-stepping, when it retries, it causes a debug exception and kprobes can not handle it because it already reset itself.

On the most of x86-64 platform, it can be easily reproduced by using kprobe tracer. E.g.

  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable
Quoted 7 lines

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

Good catch!

Acked-by on v1 1/1
#2 lore raw
In reply to Masami Hiramatsu
Quoted 20 lines · On Sat, 11 Jun 2016 23:06:53 +0900 Masami Hiramatsu <[email protected]> wrote:

Fix kprobe_fault_handler to clear TF (trap flag) bit of flags register in the case of fault fixup on single-stepping.

If we put a kprobe on the instruction which can cause a page fault (e.g. actual mov instructions in copy_user_*), that fault happens on a single-stepping buffer. In this case, kprobes resets running instance so that the CPU can retry execution on the original ip address. However, current code forgets reset TF bit. Since this fault happens with TF bit set for enabling single-stepping, when it retries, it causes a debug exception and kprobes can not handle it because it already reset itself.

On the most of x86-64 platform, it can be easily reproduced by using kprobe tracer. E.g.

  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable
Quoted 6 lines

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

This should definitely be marked for stable, and I bisected it all the way down to this commit: f4cb1cc18f364d "x86-64, copy_user: Remove zero byte check before copy user buffer."

I reverted that commit and sure enough, this bug goes away. I'm not saying the revert should be done. I'm just doing an FYI, and showing how changes that appear to be a nice clean up can have subtle effects. I'm not even sure how that change caused this to be a problem with kprobes.

The proper fix is this patch.

Please add:

Cc [email protected] # v3.14+

-- Steve

Quoted 23 lines
---
 arch/x86/kernel/kprobes/core.c |    7 +++++++
 1 file changed, 7 insertions(+)
arch/x86/kernel/kprobes/core.c +7 -0
index 38cf7a7..856df81 100644
@@ -961,6 +961,13 @@ int kprobe_fault_handler(struct pt_regs *regs, int trapnr)
 		 * normal page fault.
 		 */
 		regs->ip = (unsigned long)cur->addr;
+		/*
+		 * Trap flag has been set here because this fault happened
+		 * where the single stepping will be done. So clear it with
+		 * resetting current kprobe.
+		 */
+		regs->flags &= ~X86_EFLAGS_TF;
+		/* If the TF was set before the kprobe hit, don't touch it */
 		regs->flags |= kcb->kprobe_old_flags;
 		if (kcb->kprobe_status == KPROBE_REENTER)
 			restore_previous_kprobe(kcb);
#3 lore raw
Quoted 11 lines · On Mon, 13 Jun 2016 19:13:45 -0400 Steven Rostedt <[email protected]> wrote:
Quoted 10 lines
  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

Quoted 9 lines

This should definitely be marked for stable, and I bisected it all the way down to this commit: f4cb1cc18f364d "x86-64, copy_user: Remove zero byte check before copy user buffer."

I reverted that commit and sure enough, this bug goes away. I'm not saying the revert should be done. I'm just doing an FYI, and showing how changes that appear to be a nice clean up can have subtle effects. I'm not even sure how that change caused this to be a problem with kprobes.

Nevermind, reverting that commit only moved the location of the "rep movsb" that you were placing the kprobe on. When I do:

echo "p copy_user_enhanced_fast_string+9" > kprobe_events

I get the same result.

That means we need to make that stable tag even earlier.

-- Steve

#4 lore raw
Quoted 11 lines · On Mon, 13 Jun 2016 19:20:19 -0400 Steven Rostedt <[email protected]> wrote:
Quoted 7 lines · On Mon, 13 Jun 2016 19:13:45 -0400 Steven Rostedt <[email protected]> wrote:
Quoted 7 lines
  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

Quoted 7 lines
Quoted 7 lines
Quoted 3 lines

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

This should definitely be marked for stable, and I bisected it all the way down to this commit: f4cb1cc18f364d "x86-64, copy_user: Remove zero byte check before copy user buffer."

I agree this is for stable.

Quoted 6 lines
Quoted 5 lines

I reverted that commit and sure enough, this bug goes away. I'm not saying the revert should be done. I'm just doing an FYI, and showing how changes that appear to be a nice clean up can have subtle effects. I'm not even sure how that change caused this to be a problem with kprobes.

Quoted 8 lines

Nevermind, reverting that commit only moved the location of the "rep movsb" that you were placing the kprobe on. When I do:

echo "p copy_user_enhanced_fast_string+9" > kprobe_events

I get the same result.

That means we need to make that stable tag even earlier.

Yeah, I think it may never be tested from beginning, so more thant 10 years we have this issue on the kernel. I recommend this for all the stable/long term support kernels.

Thank you,

Signature · 1 line
Masami Hiramatsu <[email protected]>
#5 lore raw
In reply to Steven Rostedt
Quoted 25 lines · Steven Rostedt
Quoted 11 lines · On Mon, 13 Jun 2016 19:13:45 -0400 Steven Rostedt <[email protected]> wrote:
Quoted 10 lines
  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

Quoted 9 lines

This should definitely be marked for stable, and I bisected it all the way down to this commit: f4cb1cc18f364d "x86-64, copy_user: Remove zero byte check before copy user buffer."

I reverted that commit and sure enough, this bug goes away. I'm not saying the revert should be done. I'm just doing an FYI, and showing how changes that appear to be a nice clean up can have subtle effects. I'm not even sure how that change caused this to be a problem with kprobes.

Quoted 8 lines

Nevermind, reverting that commit only moved the location of the "rep movsb" that you were placing the kprobe on. When I do:

echo "p copy_user_enhanced_fast_string+9" > kprobe_events

I get the same result.

That means we need to make that stable tag even earlier.

I added:

Cc [email protected] # All the way back to ancient kernels

Thanks,

Ingo

#6 lore raw

[tip:perf/urgent] kprobes/x86: Clear TF bit in fault on single-stepping

In reply to Masami Hiramatsu
kprobes/x86: Clear TF bit in fault on single-stepping
Commit-ID:  dcfc47248d3f7d28df6f531e6426b933de94370d
Gitweb:     http://git.kernel.org/tip/dcfc47248d3f7d28df6f531e6426b933de94370d
Author:     Masami Hiramatsu <[email protected]>
AuthorDate: Sat, 11 Jun 2016 23:06:53 +0900
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 14 Jun 2016 12:00:54 +0200

kprobes/x86: Clear TF bit in fault on single-stepping

Fix kprobe_fault_handler() to clear the TF (trap flag) bit of the flags register in the case of a fault fixup on single-stepping.

If we put a kprobe on the instruction which caused a page fault (e.g. actual mov instructions in copy_user_*), that fault happens on the single-stepping buffer. In this case, kprobes resets running instance so that the CPU can retry execution on the original ip address.

However, current code forgets to reset the TF bit. Since this fault happens with TF bit set for enabling single-stepping, when it retries, it causes a debug exception and kprobes can not handle it because it already reset itself.

On the most of x86-64 platform, it can be easily reproduced by using kprobe tracer. E.g.

  # cd /sys/kernel/debug/tracing
  # echo p copy_user_enhanced_fast_string+5 > kprobe_events
  # echo 1 > events/kprobes/enable

And you'll see a kernel panic on do_debug(), since the debug trap is not handled by kprobes.

To fix this problem, we just need to clear the TF bit when resetting running kprobe.

Cc Alexander Shishkin <[email protected]>
Cc Andy Lutomirski <[email protected]>
Cc Arnaldo Carvalho de Melo <[email protected]>
Cc Borislav Petkov <[email protected]>
Cc Brian Gerst <[email protected]>
Cc Denys Vlasenko <[email protected]>
Cc H. Peter Anvin <[email protected]>
Cc Jiri Olsa <[email protected]>
Cc Linus Torvalds <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Stephane Eranian <[email protected]>
Cc Thomas Gleixner <[email protected]>
Cc Vince Weaver <[email protected]>
Cc [email protected] # All the way back to ancient kernels
[ Updated the comments. ]
---
 arch/x86/kernel/kprobes/core.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)
arch/x86/kernel/kprobes/core.c +12 -0
index 38cf7a7..7847e5c 100644
@@ -961,7 +961,19 @@ int kprobe_fault_handler(struct pt_regs *regs, int trapnr)
 		 * normal page fault.
 		 */
 		regs->ip = (unsigned long)cur->addr;
+		/*
+		 * Trap flag (TF) has been set here because this fault
+		 * happened where the single stepping will be done.
+		 * So clear it by resetting the current kprobe:
+		 */
+		regs->flags &= ~X86_EFLAGS_TF;
+
+		/*
+		 * If the TF flag was set before the kprobe hit,
+		 * don't touch it:
+		 */
 		regs->flags |= kcb->kprobe_old_flags;
+
 		if (kcb->kprobe_status == KPROBE_REENTER)
 			restore_previous_kprobe(kcb);
 		else