[RFC v2 3/3] arm64: kprobes: Save and restore saved_irqflag in prev_kprobe

From: Pu Hu

Date: Thu Jul 09 2026 - 10:32:33 EST


From: Pu Hu <hupu@xxxxxxxxxxxxx>

When a kprobe is reentered from KPROBE_HIT_SS state (allowed by the
previous patch), setup_singlestep() for the nested kprobe calls
kprobes_save_local_irqflag(), overwriting kcb->saved_irqflag with the
currently masked DAIF value. The outer kprobe's original DAIF state is
lost.

When the nested kprobe completes, restore_previous_kprobe() brings back
the outer kprobe's kp and status, but saved_irqflag still contains the
nested kprobe's value. When the outer kprobe's single-step eventually
finishes, kprobes_restore_local_irqflag() applies the wrong DAIF mask,
leaving interrupts permanently disabled.

Fix this by extending struct prev_kprobe with a saved_irqflag field, and
saving/restoring it alongside kp and status. This ensures the outer
kprobe's original interrupt state is preserved across reentry.

Signed-off-by: Pu Hu <hupu@xxxxxxxxxxxxx>
Signed-off-by: Hongyan Xia <hongyan.xia@xxxxxxxxxxxxx>
---
arch/arm64/include/asm/kprobes.h | 6 ++++++
arch/arm64/kernel/probes/kprobes.c | 15 +++++++++++++++
2 files changed, 21 insertions(+)

diff --git a/arch/arm64/include/asm/kprobes.h b/arch/arm64/include/asm/kprobes.h
index f2782560647b..35ce2c94040e 100644
--- a/arch/arm64/include/asm/kprobes.h
+++ b/arch/arm64/include/asm/kprobes.h
@@ -26,6 +26,12 @@
struct prev_kprobe {
struct kprobe *kp;
unsigned int status;
+
+ /*
+ * The original DAIF state of the outer kprobe, saved here before
+ * a nested kprobe overwrites kcb->saved_irqflag during reentry.
+ */
+ unsigned long saved_irqflag;
};

/* per-cpu kprobe control block */
diff --git a/arch/arm64/kernel/probes/kprobes.c b/arch/arm64/kernel/probes/kprobes.c
index 2ca5916eca2f..4e0efad5caf2 100644
--- a/arch/arm64/kernel/probes/kprobes.c
+++ b/arch/arm64/kernel/probes/kprobes.c
@@ -174,12 +174,27 @@ static void __kprobes save_previous_kprobe(struct kprobe_ctlblk *kcb)
{
kcb->prev_kprobe.kp = kprobe_running();
kcb->prev_kprobe.status = kcb->kprobe_status;
+
+ /*
+ * Save the outer kprobe's original DAIF flags before the nested
+ * kprobe calls kprobes_save_local_irqflag() and overwrites
+ * kcb->saved_irqflag. Without this, the outer kprobe will restore
+ * the wrong DAIF state and leave interrupts permanently masked.
+ */
+ kcb->prev_kprobe.saved_irqflag = kcb->saved_irqflag;
}

static void __kprobes restore_previous_kprobe(struct kprobe_ctlblk *kcb)
{
__this_cpu_write(current_kprobe, kcb->prev_kprobe.kp);
kcb->kprobe_status = kcb->prev_kprobe.status;
+
+ /*
+ * Restore the outer kprobe's saved_irqflag so that when its
+ * single-step completes, kprobes_restore_local_irqflag() uses
+ * the correct original DAIF value.
+ */
+ kcb->saved_irqflag = kcb->prev_kprobe.saved_irqflag;
}

static void __kprobes set_current_kprobe(struct kprobe *p)
--
2.43.0