[PATCH] x86/mce: Serialize sysfs controls against CPU hotplug

From: Thomas Gleixner

Date: Fri Oct 23 2026 - 05:20:27 EST


syzbot reported a NULL pointer dereference in the CPU hotplug code. It
is caused by the lack of synchronizaton between CPU hotplug and MCE
sysfs controls, which allows the sysfs controls to reenable not only the
timer, but also the CMCI reporting on a CPU which is about to go out. A
similar issue exists in the CPU online path where the timer can be
enabled via sysfs control before the CPU runs the initialization of the
timer.

Prevent this by:

1) Acquiring the mce_sysfs_mutex in the hotplug callbacks

2) Excluding CPUs which do not have MCE fully initialized from the sysfs
control IPIs.

That covers both the CPU up and down situations.

Fixes: 9af43b54ab45 ("x86, mce: sysfs entries for new mce options")
Reported-by: syzbot+74de56995244fe32ffe2@xxxxxxxxxxxxxxxxxxxxxxxxx
Signed-off-by: Thomas Gleixner <tglx@xxxxxxxxxx>
Closes: https://lore.kernel.org/all/6a7ec39f.5b0d2c79.2ef4ef.0002.GAE@xxxxxxxxxx
---
arch/x86/kernel/cpu/mce/core.c | 20 +++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)

--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -2447,6 +2447,12 @@ static struct syscore mce_syscore = {
/*
* mce_device: Sysfs support
*/
+static cpumask_var_t mce_device_initialized;
+
+static inline void on_each_enabled_cpu(smp_call_func_t func, void *arg)
+{
+ on_each_cpu_cond_mask(NULL, func, arg, true, mce_device_initialized);
+}

static void mce_cpu_restart(void *data)
{
@@ -2462,7 +2468,7 @@ static void mce_cpu_restart(void *data)
static void mce_restart(void)
{
mce_timer_delete_all();
- on_each_cpu(mce_cpu_restart, NULL, 1);
+ on_each_enabled_cpu(mce_cpu_restart, NULL);
mce_schedule_work();
}

@@ -2553,12 +2559,12 @@ static ssize_t set_ignore_ce(struct devi
if (new) {
/* disable ce features */
mce_timer_delete_all();
- on_each_cpu(mce_disable_cmci, NULL, 1);
+ on_each_enabled_cpu(mce_disable_cmci, NULL);
mca_cfg.ignore_ce = true;
} else {
/* enable ce features */
mca_cfg.ignore_ce = false;
- on_each_cpu(mce_enable_ce, (void *)1, 1);
+ on_each_enabled_cpu(mce_enable_ce, (void *)1);
}
}
mutex_unlock(&mce_sysfs_mutex);
@@ -2579,12 +2585,12 @@ static ssize_t set_cmci_disabled(struct
if (mca_cfg.cmci_disabled ^ !!new) {
if (new) {
/* disable cmci */
- on_each_cpu(mce_disable_cmci, NULL, 1);
+ on_each_enabled_cpu(mce_disable_cmci, NULL);
mca_cfg.cmci_disabled = true;
} else {
/* enable cmci */
mca_cfg.cmci_disabled = false;
- on_each_cpu(mce_enable_ce, NULL, 1);
+ on_each_enabled_cpu(mce_enable_ce, NULL);
}
}
mutex_unlock(&mce_sysfs_mutex);
@@ -2641,8 +2647,6 @@ static struct device_attribute *mce_devi
NULL
};

-static cpumask_var_t mce_device_initialized;
-
static void mce_device_release(struct device *dev)
{
kfree(dev);
@@ -2759,6 +2763,7 @@ static int mce_cpu_online(unsigned int c
{
struct timer_list *t = this_cpu_ptr(&mce_timer);

+ guard(mutex)(&mce_sysfs_mutex);
mce_device_create(cpu);
mce_threshold_create_device(cpu);
mce_reenable_cpu();
@@ -2770,6 +2775,7 @@ static int mce_cpu_pre_down(unsigned int
{
struct timer_list *t = this_cpu_ptr(&mce_timer);

+ guard(mutex)(&mce_sysfs_mutex);
mce_disable_cpu();
timer_delete_sync(t);
mce_threshold_remove_device(cpu);