CVE-2026-98395: powerpc/entry: Fix irq_soft_mask corruption on replayed interrupt exit
In the Linux kernel, the following vulnerability has been resolved:
powerpc/entry: Fix irqsoftmask corruption on replayed interrupt exit
When replaysoftinterrupts() replays a pending interrupt (e.g. PACAIRQDEC -> timerinterrupt), it calls the handler directly with a synthetic ptregs. The DEFINEINTERRUPTHANDLERASYNC wrapper around each handler calls archinterruptasyncexitprepare() on the way out, which calls archinterruptexitprepare() -> localirqdisable() -> archlocalirqdisable(), which does:
irqsoftmaskset(IRQSDISABLED) / 0x1 /
This unconditionally overwrites irqsoftmask with IRQSDISABLED (0x1), stripping the IRQSPMIDISABLED (0x2) bit. The result is that irqsoftmask is 0x1 instead of IRQSALLDISABLED (0x3) when the handler returns to replaysoftinterrupts().
For a normally-taken interrupt this is harmless: the next interrupt always enters through archinterruptenterprepare() which unconditionally sets irqsoftmask to IRQSALLDISABLED. But during replay, nextinterrupt() is called directly between replayed handlers without going back through archinterruptenterprepare(), so the stripped bit is never restored. nextinterrupt() then fires a WARNING:
WARNING: arch/powerpc/kernel/irq64.c:75 WARNON(irqsoftmaskreturn() != IRQSALLDISABLED)
The warning was observed early in boot on a POWER10 pseries guest during kmemcacheinitlate(), where a spinlock release triggers interrupt replay that processes a pending timer interrupt.
Debugger state confirming the bug: Before timerinterrupt(®s): irqsoftmask = 0x3 (IRQSALLDISABLED) correct irqhappened = 0x41 (HARDDIS|REPLAYING) correct After timerinterrupt(®s) returns: irqsoftmask = 0x1 (IRQSDISABLED) WRONG - PMI bit stripped irqhappened = 0x41 unchanged
The fix is to replace localirqdisable() with hardirqdisable().
hardirqdisable() is the right primitive here for two reasons:
1. On PPC64 (hwirq.h:301) it calls irqsoftmasksetreturn(IRQSALLDISABLED), setting the soft mask to 0x3 (both IRQSDISABLED and IRQSPMIDISABLED), which preserves the PMI bit and fixes the WARNING. The additional work it does (hardirqdisable(), PACAIRQHARDDIS |=) is redundant but safe since both are already set at this point in the exit path; the tracehardirqsoff() inside is guarded by if (!archirqsdisabledflags(flags)) so it will not double-fire.
2. On PPC32 (hwirq.h:467) hardirqdisable() maps to archlocalirqdisable() -> hardirqdisable(), which clears MSR[EE] in hardware. This is exactly correct: PPC32 has no soft-mask PACA mechanism, so the hardware disable is the right way to satisfy irqentryexit()'s requirement. This also fixes a build error on PPC32 where irqsoftmaskset() is only defined under CONFIGPPC64:
arch/powerpc/include/asm/entry-common.h:273: error: implicit declaration of function 'irqsoftmaskset'
Using hardirqdisable() requires no #ifdef and is consistent with how the rest of the entry code (e.g. entry-common.h:463) handles the same PPC32/PPC64 split.
Affected Software
Event History
Frequently Asked Questions
What runtime condition is required for the incorrect interrupt-mask state to persist?
A pending soft interrupt must be replayed through __replay_soft_interrupts(), where handlers are called with synthetic pt_regs. The condition persists when replay moves directly to the next handler via next_interrupt() without re-entering through arch_interrupt_enter_prepare().
Does the same mask corruption affect ordinarily delivered interrupts?
For a normally taken interrupt, the stripped IRQS_PMI_DISABLED bit is restored on the next interrupt entry because arch_interrupt_enter_prepare() sets irq_soft_mask to IRQS_ALL_DISABLED. The problematic path is replayed interrupt handling, which bypasses that entry preparation between handlers.