diff mbox series

[v4,1/2] x86/fred/signal: Prevent single-step upon ERETU completion

Message ID 20250605181020.590459-2-xin@zytor.com
State New
Headers show
Series x86/fred: Prevent single-step upon ERETU completion | expand

Commit Message

Xin Li (Intel) June 5, 2025, 6:10 p.m. UTC
Clear the software event flag in the augmented SS to prevent infinite
SIGTRAP handler loop if the trap flag (TF) is set without an external
debugger attached.

Following is a typical single-stepping flow for a user process:

1) The user process is prepared for single-stepping by setting
   RFLAGS.TF = 1.
2) When any instruction in user space completes, a #DB is triggered.
3) The kernel handles the #DB and returns to user space, invoking the
   SIGTRAP handler with RFLAGS.TF = 0.
4) After the SIGTRAP handler finishes, the user process performs a
   sigreturn syscall, restoring the original state, including
   RFLAGS.TF = 1.
5) Goto step 2.

According to the FRED specification:

A) Bit 17 in the augmented SS is designated as the software event
   flag, which is set to 1 for FRED event delivery of SYSCALL,
   SYSENTER, or INT n.
B) If bit 17 of the augmented SS is 1 and ERETU would result in
   RFLAGS.TF = 1, a single-step trap will be pending upon completion
   of ERETU.

In step 4) above, the software event flag is set upon the sigreturn
syscall, and its corresponding ERETU would restore RFLAGS.TF = 1.
This combination causes a pending single-step trap upon completion of
ERETU.  Therefore, another #DB is triggered before any user space
instruction is executed, which leads to an infinite loop in which the
SIGTRAP handler keeps being invoked on the same user space IP.

Suggested-by: H. Peter Anvin (Intel) <hpa@zytor.com>
Signed-off-by: Xin Li (Intel) <xin@zytor.com>
Cc: stable@vger.kernel.org
---

Change in v3:
*) Use "#ifdef CONFIG_X86_FRED" instead of IS_ENABLED(CONFIG_X86_FRED)
   (Intel LKP).

Change in v2:
*) Remove the check cpu_feature_enabled(X86_FEATURE_FRED), because
   regs->fred_ss.swevent will always be 0 otherwise (H. Peter Anvin).
---
 arch/x86/include/asm/sighandling.h | 22 ++++++++++++++++++++++
 arch/x86/kernel/signal_32.c        |  4 ++++
 arch/x86/kernel/signal_64.c        |  4 ++++
 3 files changed, 30 insertions(+)

Comments

H. Peter Anvin June 5, 2025, 6:46 p.m. UTC | #1
On June 5, 2025 11:10:19 AM PDT, "Xin Li (Intel)" <xin@zytor.com> wrote:
>Clear the software event flag in the augmented SS to prevent infinite
>SIGTRAP handler loop if the trap flag (TF) is set without an external
>debugger attached.
>
>Following is a typical single-stepping flow for a user process:
>
>1) The user process is prepared for single-stepping by setting
>   RFLAGS.TF = 1.
>2) When any instruction in user space completes, a #DB is triggered.
>3) The kernel handles the #DB and returns to user space, invoking the
>   SIGTRAP handler with RFLAGS.TF = 0.
>4) After the SIGTRAP handler finishes, the user process performs a
>   sigreturn syscall, restoring the original state, including
>   RFLAGS.TF = 1.
>5) Goto step 2.
>
>According to the FRED specification:
>
>A) Bit 17 in the augmented SS is designated as the software event
>   flag, which is set to 1 for FRED event delivery of SYSCALL,
>   SYSENTER, or INT n.
>B) If bit 17 of the augmented SS is 1 and ERETU would result in
>   RFLAGS.TF = 1, a single-step trap will be pending upon completion
>   of ERETU.
>
>In step 4) above, the software event flag is set upon the sigreturn
>syscall, and its corresponding ERETU would restore RFLAGS.TF = 1.
>This combination causes a pending single-step trap upon completion of
>ERETU.  Therefore, another #DB is triggered before any user space
>instruction is executed, which leads to an infinite loop in which the
>SIGTRAP handler keeps being invoked on the same user space IP.
>
>Suggested-by: H. Peter Anvin (Intel) <hpa@zytor.com>
>Signed-off-by: Xin Li (Intel) <xin@zytor.com>
>Cc: stable@vger.kernel.org
>---
>
>Change in v3:
>*) Use "#ifdef CONFIG_X86_FRED" instead of IS_ENABLED(CONFIG_X86_FRED)
>   (Intel LKP).
>
>Change in v2:
>*) Remove the check cpu_feature_enabled(X86_FEATURE_FRED), because
>   regs->fred_ss.swevent will always be 0 otherwise (H. Peter Anvin).
>---
> arch/x86/include/asm/sighandling.h | 22 ++++++++++++++++++++++
> arch/x86/kernel/signal_32.c        |  4 ++++
> arch/x86/kernel/signal_64.c        |  4 ++++
> 3 files changed, 30 insertions(+)
>
>diff --git a/arch/x86/include/asm/sighandling.h b/arch/x86/include/asm/sighandling.h
>index e770c4fc47f4..b8481d33ba8e 100644
>--- a/arch/x86/include/asm/sighandling.h
>+++ b/arch/x86/include/asm/sighandling.h
>@@ -24,4 +24,26 @@ int ia32_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs);
> int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs);
> int x32_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs);
> 
>+/*
>+ * To prevent infinite SIGTRAP handler loop if the trap flag (TF) is set
>+ * without an external debugger attached, clear the software event flag in
>+ * the augmented SS, ensuring no single-step trap is pending upon ERETU
>+ * completion.
>+ *
>+ * Note, this function should be called in sigreturn() before the original
>+ * state is restored to make sure the TF is read from the entry frame.
>+ */
>+static __always_inline void prevent_single_step_upon_eretu(struct pt_regs *regs)
>+{
>+	/*
>+	 * If the trap flag (TF) is set, i.e., the sigreturn() SYSCALL instruction
>+	 * is being single-stepped, do not clear the software event flag in the
>+	 * augmented SS, thus a debugger won't skip over the following instruction.
>+	 */
>+#ifdef CONFIG_X86_FRED
>+	if (!(regs->flags & X86_EFLAGS_TF))
>+		regs->fred_ss.swevent = 0;
>+#endif
>+}
>+
> #endif /* _ASM_X86_SIGHANDLING_H */
>diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
>index 98123ff10506..42bbc42bd350 100644
>--- a/arch/x86/kernel/signal_32.c
>+++ b/arch/x86/kernel/signal_32.c
>@@ -152,6 +152,8 @@ SYSCALL32_DEFINE0(sigreturn)
> 	struct sigframe_ia32 __user *frame = (struct sigframe_ia32 __user *)(regs->sp-8);
> 	sigset_t set;
> 
>+	prevent_single_step_upon_eretu(regs);
>+
> 	if (!access_ok(frame, sizeof(*frame)))
> 		goto badframe;
> 	if (__get_user(set.sig[0], &frame->sc.oldmask)
>@@ -175,6 +177,8 @@ SYSCALL32_DEFINE0(rt_sigreturn)
> 	struct rt_sigframe_ia32 __user *frame;
> 	sigset_t set;
> 
>+	prevent_single_step_upon_eretu(regs);
>+
> 	frame = (struct rt_sigframe_ia32 __user *)(regs->sp - 4);
> 
> 	if (!access_ok(frame, sizeof(*frame)))
>diff --git a/arch/x86/kernel/signal_64.c b/arch/x86/kernel/signal_64.c
>index ee9453891901..d483b585c6c6 100644
>--- a/arch/x86/kernel/signal_64.c
>+++ b/arch/x86/kernel/signal_64.c
>@@ -250,6 +250,8 @@ SYSCALL_DEFINE0(rt_sigreturn)
> 	sigset_t set;
> 	unsigned long uc_flags;
> 
>+	prevent_single_step_upon_eretu(regs);
>+
> 	frame = (struct rt_sigframe __user *)(regs->sp - sizeof(long));
> 	if (!access_ok(frame, sizeof(*frame)))
> 		goto badframe;
>@@ -366,6 +368,8 @@ COMPAT_SYSCALL_DEFINE0(x32_rt_sigreturn)
> 	sigset_t set;
> 	unsigned long uc_flags;
> 
>+	prevent_single_step_upon_eretu(regs);
>+
> 	frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8);
> 
> 	if (!access_ok(frame, sizeof(*frame)))

Nitpick: "Prevent immediate repeat of single step trap on return from SIGTRAP handler"
diff mbox series

Patch

diff --git a/arch/x86/include/asm/sighandling.h b/arch/x86/include/asm/sighandling.h
index e770c4fc47f4..b8481d33ba8e 100644
--- a/arch/x86/include/asm/sighandling.h
+++ b/arch/x86/include/asm/sighandling.h
@@ -24,4 +24,26 @@  int ia32_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs);
 int x64_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs);
 int x32_setup_rt_frame(struct ksignal *ksig, struct pt_regs *regs);
 
+/*
+ * To prevent infinite SIGTRAP handler loop if the trap flag (TF) is set
+ * without an external debugger attached, clear the software event flag in
+ * the augmented SS, ensuring no single-step trap is pending upon ERETU
+ * completion.
+ *
+ * Note, this function should be called in sigreturn() before the original
+ * state is restored to make sure the TF is read from the entry frame.
+ */
+static __always_inline void prevent_single_step_upon_eretu(struct pt_regs *regs)
+{
+	/*
+	 * If the trap flag (TF) is set, i.e., the sigreturn() SYSCALL instruction
+	 * is being single-stepped, do not clear the software event flag in the
+	 * augmented SS, thus a debugger won't skip over the following instruction.
+	 */
+#ifdef CONFIG_X86_FRED
+	if (!(regs->flags & X86_EFLAGS_TF))
+		regs->fred_ss.swevent = 0;
+#endif
+}
+
 #endif /* _ASM_X86_SIGHANDLING_H */
diff --git a/arch/x86/kernel/signal_32.c b/arch/x86/kernel/signal_32.c
index 98123ff10506..42bbc42bd350 100644
--- a/arch/x86/kernel/signal_32.c
+++ b/arch/x86/kernel/signal_32.c
@@ -152,6 +152,8 @@  SYSCALL32_DEFINE0(sigreturn)
 	struct sigframe_ia32 __user *frame = (struct sigframe_ia32 __user *)(regs->sp-8);
 	sigset_t set;
 
+	prevent_single_step_upon_eretu(regs);
+
 	if (!access_ok(frame, sizeof(*frame)))
 		goto badframe;
 	if (__get_user(set.sig[0], &frame->sc.oldmask)
@@ -175,6 +177,8 @@  SYSCALL32_DEFINE0(rt_sigreturn)
 	struct rt_sigframe_ia32 __user *frame;
 	sigset_t set;
 
+	prevent_single_step_upon_eretu(regs);
+
 	frame = (struct rt_sigframe_ia32 __user *)(regs->sp - 4);
 
 	if (!access_ok(frame, sizeof(*frame)))
diff --git a/arch/x86/kernel/signal_64.c b/arch/x86/kernel/signal_64.c
index ee9453891901..d483b585c6c6 100644
--- a/arch/x86/kernel/signal_64.c
+++ b/arch/x86/kernel/signal_64.c
@@ -250,6 +250,8 @@  SYSCALL_DEFINE0(rt_sigreturn)
 	sigset_t set;
 	unsigned long uc_flags;
 
+	prevent_single_step_upon_eretu(regs);
+
 	frame = (struct rt_sigframe __user *)(regs->sp - sizeof(long));
 	if (!access_ok(frame, sizeof(*frame)))
 		goto badframe;
@@ -366,6 +368,8 @@  COMPAT_SYSCALL_DEFINE0(x32_rt_sigreturn)
 	sigset_t set;
 	unsigned long uc_flags;
 
+	prevent_single_step_upon_eretu(regs);
+
 	frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8);
 
 	if (!access_ok(frame, sizeof(*frame)))