In the Linux kernel, the following vulnerability has been resolved:
perf/x86: Move event pointer setup earlier in x86pmuenable()
A production AMD EPYC system crashed with a NULL pointer dereference in the PMU NMI handler:
BUG: kernel NULL pointer dereference, address: 0000000000000198 RIP: x86perfeventupdate+0xc/0xa0 Call Trace: <NMI> amdpmuv2handleirq+0x1a6/0x390 perfeventnmihandler+0x24/0x40
The faulting instruction is cmpq $0x0, 0x198(%rdi) with RDI=0,
corresponding to the if (unlikely(!hwc->event_base)) check in
x86perfevent_update() where hwc = &event->hw and event is NULL.
drgn inspection of the vmcore on CPU 106 showed a mismatch between cpuc->active_mask and cpuc->events[]:
activemask: 0x1e (bits 1, 2, 3, 4) events[1]: 0xff1100136cbd4f38 (valid) events[2]: 0x0 (NULL, but activemask bit 2 set) events[3]: 0xff1100076fd2cf38 (valid) events[4]: 0xff1100079e990a90 (valid)
The event that should occupy events[2] was found in eventlist[2] with hw.idx=2 and hw.state=0x0, confirming x86pmustart() had run (which clears hw.state and sets activemask) but events[2] was never populated.
Another event (event_list[0]) had hw.state=0x7 (STOPPED|UPTODATE|ARCH), showing it was stopped when the PMU rescheduled events, confirming the throttle-then-reschedule sequence occurred.
The root cause is commit 7e772a93eb61 ("perf/x86: Fix NULL event access and potential PEBS record loss") which moved the cpuc->events[idx] assignment out of x86pmustart() and into step 2 of x86pmuenable(), after the PERFHESARCH check. This broke any path that calls pmu->start() without going through x86pmuenable() -- specifically the unthrottle path:
perfadjustfrequnthrevents() -> perfeventunthrottlegroup() -> perfeventunthrottle() -> event->pmu->start(event, 0) -> x86pmustart() // sets activemask but not events[]
The race sequence is:
A group of perf events overflows, triggering group throttle via perfeventthrottlegroup(). All events are stopped: activemask bits cleared, events[] preserved (x86pmustop no longer clears events[] after commit 7e772a93eb61).
While still throttled (PERFHESSTOPPED), x86pmuenable() runs due to other scheduling activity. Stopped events that need to move counters get PERFHESARCH set and events[oldidx] cleared. In step 2 of x86pmuenable(), PERFHESARCH causes these events to be skipped -- events[newidx] is never set.
The timer tick unthrottles the group via pmu->start(). Since commit 7e772a93eb61 removed the events[] assignment from x86pmustart(), activemask[newidx] is set but events[new_idx] remains NULL.
A PMC overflow NMI fires. The handler iterates active counters, finds active_mask[2] set, reads events[2] which is NULL, and crashes dereferencing it.
Move the cpuc->events[hwc->idx] assignment in x86pmuenable() to before the PERFHESARCH check, so that events[] is populated even for events that are not immediately started. This ensures the unthrottle path via pmu->start() always finds a valid event pointer.
{
"osv_generated_from": "https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/23xxx/CVE-2026-23435.json",
"cna_assigner": "Linux"
}