* [PATCH v2 1/3] perf/amd/ibs: Reset CPU0 IBS state on syscore resume
2026-10-26 4:18 [PATCH v2 0/3] perf/amd/ibs: Assorted fixes Ravi Bangoria
@ 2026-10-26 4:18 ` Ravi Bangoria
2026-10-26 4:18 ` [PATCH v2 2/3] perf/amd/ibs: Fix kernel address leak via REGS_USER and REGS_INTR Ravi Bangoria
2026-10-26 4:18 ` [PATCH v3 3/3] perf/amd/ibs: Simplify event state handling with IBS_CAPS_DIS Ravi Bangoria
2 siblings, 0 replies; 7+ messages in thread
From: Ravi Bangoria @ 2026-10-26 4:18 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Jim Mattson
Cc: Ravi Bangoria, Jayapal Ashwin Nair, Gennady Kupava, Dapeng Mi,
Arnaldo Carvalho de Melo, Namhyung Kim, Ian Rogers, x86,
linux-perf-users, linux-kernel, Santosh Shukla, Ananth Narayan,
Sandipan Das, Manali Shukla
During a suspend/resume cycle, all CPUs except CPU0 go through hotplug
callbacks, which sets up IBS state. CPU0 bypasses this path, leaving its
IBS state stale after resume. Fix this by explicitly setting CPU0's IBS
state in the syscore ->resume() callback.
Fixes: efa5700ec0da ("perf/amd/ibs: Support IBS_{FETCH|OP}_CTL2[Dis] to eliminate RMW race")
Signed-off-by: Ravi Bangoria <ravi.bangoria@amd.com>
Reviewed-by: Jim Mattson <jmattson@google.com>
---
arch/x86/events/amd/ibs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/events/amd/ibs.c b/arch/x86/events/amd/ibs.c
index 3531f9c23b8c..bfb51f8e184f 100644
--- a/arch/x86/events/amd/ibs.c
+++ b/arch/x86/events/amd/ibs.c
@@ -1948,7 +1948,7 @@ static int perf_ibs_suspend(void *data)
static void perf_ibs_resume(void *data)
{
ibs_eilvt_setup();
- setup_APIC_ibs();
+ x86_pmu_amd_ibs_starting_cpu(smp_processor_id());
}
static const struct syscore_ops perf_ibs_syscore_ops = {
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v2 2/3] perf/amd/ibs: Fix kernel address leak via REGS_USER and REGS_INTR
2026-10-26 4:18 [PATCH v2 0/3] perf/amd/ibs: Assorted fixes Ravi Bangoria
2026-10-26 4:18 ` [PATCH v2 1/3] perf/amd/ibs: Reset CPU0 IBS state on syscore resume Ravi Bangoria
@ 2026-10-26 4:18 ` Ravi Bangoria
2026-10-26 5:52 ` Jayapal Ashwin Nair
` (2 more replies)
2026-10-26 4:18 ` [PATCH v3 3/3] perf/amd/ibs: Simplify event state handling with IBS_CAPS_DIS Ravi Bangoria
2 siblings, 3 replies; 7+ messages in thread
From: Ravi Bangoria @ 2026-10-26 4:18 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Jim Mattson
Cc: Ravi Bangoria, Jayapal Ashwin Nair, Gennady Kupava, Dapeng Mi,
Arnaldo Carvalho de Melo, Namhyung Kim, Ian Rogers, x86,
linux-perf-users, linux-kernel, Santosh Shukla, Ananth Narayan,
Sandipan Das, Manali Shukla
IBS hardware captures a sample and raises an NMI. Since the NMI can be
delivered with a delay, a privilege change may occur between sample
capture and NMI delivery. Because IBS only provides the sample RIP, the
kernel records GPRs from the NMI pt_regs, which may reflect kernel mode
if a privilege switch occurred in the interim. This can leak kernel
addresses to unprivileged users through PERF_SAMPLE_REGS_USER and/or
PERF_SAMPLE_REGS_INTR.
Fix this by using interrupt regs while preparing PERF_SAMPLE_REGS_USER
and PERF_SAMPLE_REGS_INTR. With the change, when IBS samples a user mode
instruction but the NMI is delivered in kernel mode, PERF_SAMPLE_REGS_USER
will source its register state from task_pt_regs(current) and
PERF_SAMPLE_REGS_INTR will be omitted from the data sent to userspace.
In addition to fixing the kernel address leak, this change also ensures
GPR snapshot consistency, which resolves the DWARF unwinding issue with
IBS.
Reported-by: Gennady Kupava <gennady.kupava@gmail.com>
Closes: https://lore.kernel.org/r/CAPu-DQqF0aF6=GS8Z6KKWeeX_V5LiXeKU_rJQZC+uGg8zuTPNw@mail.gmail.com
Reported-by: Jayapal Ashwin Nair <janair@amd.com>
Fixes: d29e744c7167 ("perf/x86: Relax privilege filter restriction on AMD IBS")
Signed-off-by: Ravi Bangoria <ravi.bangoria@amd.com>
---
arch/x86/events/amd/ibs.c | 50 ++++++++++++++++++++++++++++++++++++++
include/linux/perf_event.h | 6 +++++
kernel/events/core.c | 10 ++++----
3 files changed, 61 insertions(+), 5 deletions(-)
diff --git a/arch/x86/events/amd/ibs.c b/arch/x86/events/amd/ibs.c
index bfb51f8e184f..78a36ebf2d23 100644
--- a/arch/x86/events/amd/ibs.c
+++ b/arch/x86/events/amd/ibs.c
@@ -1533,6 +1533,56 @@ static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs)
throttle = perf_event_account_interrupt(event);
goto out;
}
+
+ /*
+ * IBS captured RIP is out of sync with NMI pt_regs due to NMI
+ * delivery latency. Use NMI pt_regs for PERF_SAMPLE_REGS_USER.
+ *
+ * Note: Valid user regs are required for PERF_SAMPLE_STACK_USER.
+ */
+ if (event->attr.sample_type & (PERF_SAMPLE_REGS_USER |
+ PERF_SAMPLE_STACK_USER)) {
+ perf_sample_regs_user(&data.regs_user, iregs);
+
+ /*
+ * Don't update dyn_size if user has not requrested for
+ * PERF_SAMPLE_REGS_USER.
+ */
+ if (event->attr.sample_type & PERF_SAMPLE_REGS_USER) {
+ /* regs dump ABI info */
+ int size = sizeof(u64);
+
+ if (data.regs_user.regs) {
+ u64 mask = event->attr.sample_regs_user;
+ size += hweight64(mask) * sizeof(u64);
+ }
+
+ data.dyn_size += size;
+ }
+
+ data.sample_flags |= PERF_SAMPLE_REGS_USER;
+ }
+
+ /*
+ * IBS captured RIP is out of sync with NMI pt_regs due to NMI
+ * delivery latency. Use NMI pt_regs for PERF_SAMPLE_REGS_INTR.
+ */
+ if (event->attr.sample_type & PERF_SAMPLE_REGS_INTR) {
+ /* regs dump ABI info */
+ int size = sizeof(u64);
+
+ perf_sample_regs_intr(&data.regs_intr, iregs,
+ event->attr.exclude_kernel);
+
+ if (data.regs_intr.regs) {
+ u64 mask = event->attr.sample_regs_intr;
+ size += hweight64(mask) * sizeof(u64);
+ }
+
+ data.dyn_size += size;
+ data.sample_flags |= PERF_SAMPLE_REGS_INTR;
+ }
+
/*
* Prevent leaking physical addresses to unprivileged users. Skip
* PERF_SAMPLE_PHYS_ADDR check since generic code prevents it for
diff --git a/include/linux/perf_event.h b/include/linux/perf_event.h
index 2d5e7687875b..ca495f6b03c4 100644
--- a/include/linux/perf_event.h
+++ b/include/linux/perf_event.h
@@ -1791,6 +1791,12 @@ extern unsigned long perf_misc_flags(struct perf_event *event, struct pt_regs *r
extern unsigned long perf_instruction_pointer(struct perf_event *event,
struct pt_regs *regs);
+extern void perf_sample_regs_user(struct perf_regs *regs_user,
+ struct pt_regs *regs);
+extern void perf_sample_regs_intr(struct perf_regs *regs_intr,
+ struct pt_regs *regs,
+ bool exclude_kernel);
+
#ifndef perf_arch_misc_flags
# define perf_arch_misc_flags(regs) \
(user_mode(regs) ? PERF_RECORD_MISC_USER : PERF_RECORD_MISC_KERNEL)
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 7846d70be57f..69b767ace309 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -7846,8 +7846,8 @@ perf_output_sample_regs(struct perf_output_handle *handle,
}
}
-static void perf_sample_regs_user(struct perf_regs *regs_user,
- struct pt_regs *regs)
+void perf_sample_regs_user(struct perf_regs *regs_user,
+ struct pt_regs *regs)
{
if (user_mode(regs)) {
regs_user->abi = perf_reg_abi(current);
@@ -7860,9 +7860,9 @@ static void perf_sample_regs_user(struct perf_regs *regs_user,
}
}
-static void perf_sample_regs_intr(struct perf_regs *regs_intr,
- struct pt_regs *regs,
- bool exclude_kernel)
+void perf_sample_regs_intr(struct perf_regs *regs_intr,
+ struct pt_regs *regs,
+ bool exclude_kernel)
{
/*
* Hardware skid can lead to a scenario where a PMI is
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH v3 3/3] perf/amd/ibs: Simplify event state handling with IBS_CAPS_DIS
2026-10-26 4:18 [PATCH v2 0/3] perf/amd/ibs: Assorted fixes Ravi Bangoria
2026-10-26 4:18 ` [PATCH v2 1/3] perf/amd/ibs: Reset CPU0 IBS state on syscore resume Ravi Bangoria
2026-10-26 4:18 ` [PATCH v2 2/3] perf/amd/ibs: Fix kernel address leak via REGS_USER and REGS_INTR Ravi Bangoria
@ 2026-10-26 4:18 ` Ravi Bangoria
2 siblings, 0 replies; 7+ messages in thread
From: Ravi Bangoria @ 2026-10-26 4:18 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Jim Mattson
Cc: Ravi Bangoria, Jayapal Ashwin Nair, Gennady Kupava, Dapeng Mi,
Arnaldo Carvalho de Melo, Namhyung Kim, Ian Rogers, x86,
linux-perf-users, linux-kernel, Santosh Shukla, Ananth Narayan,
Sandipan Das, Manali Shukla
The IBS_{FETCH|OP}_CTL2[Dis] bit allows IBS sampling to be stopped without
a RMW of IBS_{FETCH,OP}_CTL. Systems providing this bit therefore do not
require the IBS_STARTED and IBS_STOPPED states used to coordinate control
MSR updates with late NMIs.
Add a separate stop path for these systems. Preserve Val while disabling
the event so that a delayed NMI can be identified and consumed, and wait
briefly for such an NMI before restarting the event.
Leave the existing handling unchanged on systems without IBS_CAPS_DIS.
Reported-by: Jim Mattson <jmattson@google.com>
Closes: https://lore.kernel.org/r/20261007234815.4026028-1-jmattson@google.com
Fixes: efa5700ec0da ("perf/amd/ibs: Support IBS_{FETCH|OP}_CTL2[Dis] to eliminate RMW race")
Signed-off-by: Ravi Bangoria <ravi.bangoria@amd.com>
Reviewed-by: Jim Mattson <jmattson@google.com>
---
arch/x86/events/amd/ibs.c | 145 ++++++++++++++++++++++++++++++++------
1 file changed, 122 insertions(+), 23 deletions(-)
diff --git a/arch/x86/events/amd/ibs.c b/arch/x86/events/amd/ibs.c
index 78a36ebf2d23..714589570c21 100644
--- a/arch/x86/events/amd/ibs.c
+++ b/arch/x86/events/amd/ibs.c
@@ -28,6 +28,7 @@ static u32 ibs_caps;
#include <linux/hardirq.h>
#include <asm/nmi.h>
+#include <asm/delay.h>
#include <asm/amd/ibs.h>
/* attr.config2 */
@@ -73,7 +74,12 @@ static u32 ibs_caps;
*
* XXX: we could probably be using !atomic bitops for all this.
*/
-
+/*
+ * NOTE: IBS_CAPS_DIS eliminates the RMW race on the IBS_{FETCH|OP}_CTL, making
+ * IBS_STARTED and IBS_STOPPED states unnecessary. We use only IBS_ENABLED and
+ * IBS_STOPPING states when IBS_CAPS_DIS is present. Semantics of IBS_STOPPING
+ * changes a bit as well.
+ */
enum ibs_states {
IBS_ENABLED = 0,
IBS_STARTED = 1,
@@ -557,7 +563,9 @@ static void perf_ibs_start(struct perf_event *event, int flags)
struct hw_perf_event *hwc = &event->hw;
struct perf_ibs *perf_ibs = container_of(event->pmu, struct perf_ibs, pmu);
struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu);
+ unsigned int delay = 50;
u64 period, config = 0;
+ u64 ctl;
if (WARN_ON_ONCE(!(hwc->state & PERF_HES_STOPPED)))
return;
@@ -575,23 +583,87 @@ static void perf_ibs_start(struct perf_event *event, int flags)
}
config |= period >> 4;
- /*
- * Reset the IBS_{FETCH|OP}_CTL MSR before updating pcpu->state.
- * Doing so prevents a race condition in which an NMI due to other
- * source might accidentally activate the event before we enable
- * it ourselves.
- */
- perf_ibs_disable_event(perf_ibs, hwc, 0);
+ if (ibs_caps & IBS_CAPS_DIS) {
+ /*
+ * IBS HW SW
+ *
+ * o Capture sample o ->stop()
+ * o Set VAL bit CTL2[Dis] = 1
+ * o Raise NMI --. o ...
+ * \ o ->start()
+ * \ wrmsr(IBS_{FETCH|OP}_CTL, new value);
+ * \ (wrmsr clears the VAL bit)
+ * `--> o Receives delayed NMI
+ * (Unknown NMI since VAL bit is cleared)
+ *
+ * Handle this with induced delay.
+ */
+ rdmsrq(hwc->config_base, ctl);
+ while ((ctl & perf_ibs->valid_mask) && delay--) {
+ udelay(1);
+ rdmsrq(hwc->config_base, ctl);
+ }
+ } else {
+ /*
+ * Reset the IBS_{FETCH|OP}_CTL MSR before updating pcpu->state.
+ * Doing so prevents a race condition in which an NMI due to
+ * other source might accidentally activate the event before we
+ * enable it ourselves.
+ */
+ perf_ibs_disable_event(perf_ibs, hwc, 0);
+ /*
+ * Set STARTED before enabling the hardware, such that a
+ * subsequent NMI must observe it.
+ */
+ set_bit(IBS_STARTED, pcpu->state);
+ clear_bit(IBS_STOPPING, pcpu->state);
+ }
+ perf_ibs_enable_event(perf_ibs, hwc, config);
+
+ perf_event_update_userpage(event);
+}
+
+/* ->stop() for platforms with additional CTL2[Dis] bit. */
+static void perf_ibs_stop_dis(struct perf_event *event, int flags)
+{
+ struct hw_perf_event *hwc = &event->hw;
+ struct perf_ibs *perf_ibs = container_of(event->pmu, struct perf_ibs, pmu);
+ struct cpu_perf_ibs *pcpu = this_cpu_ptr(perf_ibs->pcpu);
+ u64 config;
+ u64 ctl2;
+
+ WARN_ON_ONCE(test_bit(IBS_STOPPING, pcpu->state));
+
+ set_bit(IBS_STOPPING, pcpu->state);
+
+ rdmsrq(hwc->extra_reg.reg, ctl2);
+ if (ctl2 & perf_ibs->disable_mask)
+ goto reset_state;
+
+ wrmsrq(hwc->extra_reg.reg, perf_ibs->disable_mask);
+
+ WARN_ON_ONCE(hwc->state & PERF_HES_STOPPED);
+ hwc->state |= PERF_HES_STOPPED;
+
+ if (hwc->state & PERF_HES_UPTODATE)
+ goto reset_state;
+
+ rdmsrq(hwc->config_base, config);
+
+ config &= ~perf_ibs->valid_mask;
/*
- * Set STARTED before enabling the hardware, such that a subsequent NMI
- * must observe it.
+ * Clear the Val bit otherwise ->start() will unnecessarily
+ * wait for 50us.
*/
- set_bit(IBS_STARTED, pcpu->state);
- clear_bit(IBS_STOPPING, pcpu->state);
- perf_ibs_enable_event(perf_ibs, hwc, config);
+ if (in_nmi())
+ wrmsrq(hwc->config_base, config);
- perf_event_update_userpage(event);
+ perf_ibs_event_update(perf_ibs, event, &config);
+ hwc->state |= PERF_HES_UPTODATE;
+
+reset_state:
+ clear_bit(IBS_STOPPING, pcpu->state);
}
static void perf_ibs_stop(struct perf_event *event, int flags)
@@ -602,6 +674,9 @@ static void perf_ibs_stop(struct perf_event *event, int flags)
u64 config;
int stopping;
+ if (ibs_caps & IBS_CAPS_DIS)
+ return perf_ibs_stop_dis(event, flags);
+
if (test_and_set_bit(IBS_STOPPING, pcpu->state))
return;
@@ -1416,19 +1491,43 @@ static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs)
unsigned int msr;
u64 *buf, *config, period, new_config = 0;
int br_target_idx = -1;
+ u64 ctl2, ctl;
- if (!test_bit(IBS_STARTED, pcpu->state)) {
-fail:
+ if (ibs_caps & IBS_CAPS_DIS) {
/*
- * Catch spurious interrupts after stopping IBS: After
- * disabling IBS there could be still incoming NMIs
- * with samples that even have the valid bit cleared.
- * Mark all this NMIs as handled.
+ * ->stop() {
+ * set_bit(IBS_STOPPING);
+ * <-- NMI (race 1)
+ * CTL2[Dis]=1;
+ * ... <-- NMI (race 2)
+ * }
*/
- if (test_and_clear_bit(IBS_STOPPED, pcpu->state))
- return 1;
+ rdmsrq(perf_ibs->msr2, ctl2);
+ if (test_bit(IBS_STOPPING, pcpu->state) /* race 1 */ ||
+ (ctl2 & perf_ibs->disable_mask) /* race 2 */) {
+ rdmsrq(perf_ibs->msr, ctl);
+ if (ctl & perf_ibs->valid_mask) {
+ ctl &= ~(perf_ibs->enable_mask |
+ perf_ibs->valid_mask);
+ wrmsrq(perf_ibs->msr, ctl);
+ return 1;
+ }
+ return 0;
+ }
+ } else {
+ if (!test_bit(IBS_STARTED, pcpu->state)) {
+fail:
+ /*
+ * Catch spurious interrupts after stopping IBS: After
+ * disabling IBS there could be still incoming NMIs
+ * with samples that even have the valid bit cleared.
+ * Mark all this NMIs as handled.
+ */
+ if (test_and_clear_bit(IBS_STOPPED, pcpu->state))
+ return 1;
- return 0;
+ return 0;
+ }
}
if (WARN_ON_ONCE(!event))
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread