Re: [PATCH v4 2/9] stop_machine: Add NMI-based execution path

From: Borislav Petkov

Date: Mon Nov 09 2026 - 21:49:15 EST


On Wed, Nov 04, 2026 at 06:23:15PM +0000, Chang S. Bae wrote:
> Currently multi_cpu_stop() executes the stop function in the
> MULTI_STOP_RUN state. But NMIs can still preempt the execution. For use
> cases requiring stricter isolation from them, provide an option to run
> the stop function from NMI context.

If that were the case then we should not use stop_machine(). But
stop_machine() prevents NMIs indirectly by simply not running anything else on
the CPUs, thus leading to no NMIs. Well, mostly. We can't do anything about
firmware gunk and so on.

David's original justifcation makes a lot more sense to me:

https://lore.kernel.org/r/20260125014224.249901-2-chang.seok.bae@xxxxxxxxx

> Then, the NMI stop function must be built without instrumentation, as
> exceptions may re-enable NMIs on return via IRET. Annotate the NMI
> handler entirely with noinstr.
>
> However, objtool cannot reliably determine whether the indirect call
> target is located in a noinstr section, so it may emit false positives.
> To avoid this, temporarily lift the instrumentation restriction around
> the indirect call site and document the intention.
>
> The x86 microcode loader is currently the primary user for this.

> But other architectures are not expected to use it.

This does not belong here.

> Add a build option to make it opt-in.

Also drop all text that explains the patch. Audit your whole set pls.

> Originally-by: David Kaplan <david.kaplan@xxxxxxx>
> Suggested-by: Borislav Petkov <bp@xxxxxxxxx>
> Suggested-by: Thomas Gleixner <tglx@xxxxxxxxxx>
> Signed-off-by: Chang S. Bae <chang.seok.bae@xxxxxxxxx>
> Link: https://lore.kernel.org/lkml/20260129121729.GRaXtP2aeWkQKegxC2@fat_crate.local
> Link: https://lore.kernel.org/lkml/87wm0zl8p2.ffs@tglx
> ---
> arch/Kconfig | 3 ++
> include/linux/stop_machine.h | 16 ++++++
> kernel/stop_machine.c | 99 +++++++++++++++++++++++++++++++++++-
> 3 files changed, 117 insertions(+), 1 deletion(-)
>
> diff --git a/arch/Kconfig b/arch/Kconfig
> index 1163736de1f9..a4b62f83efb6 100644
> --- a/arch/Kconfig
> +++ b/arch/Kconfig
> @@ -1824,4 +1824,7 @@ config ARCH_HAS_CPU_ATTACK_VECTORS
> config HAVE_ARCH_GET_SECUREBOOT
> def_bool EFI
>
> +config STOP_MACHINE_NMI
> + bool

What is the real point for that ugly ifdeffery introducing, yet another
Kconfig option in the zoo?

> endmenu
> diff --git a/include/linux/stop_machine.h b/include/linux/stop_machine.h
> index 84e7fb627ba4..b53b028296e0 100644
> --- a/include/linux/stop_machine.h
> +++ b/include/linux/stop_machine.h
> @@ -20,6 +20,12 @@
> */
> typedef int (*cpu_stop_fn_t)(void *arg);
>
> +/*
> + * Stop function variant runnable from NMI context. This makes the
> + * noinstr requirement explicit at the type level.
> + */

I don't see what the point of that comment is.

> +typedef int (*cpu_stop_nmisafe_fn_t)(void *arg);
> +
> #ifdef CONFIG_SMP
>
> struct cpu_stop_work {
> @@ -186,4 +192,14 @@ stop_machine_from_inactive_cpu(cpu_stop_fn_t fn, void *data,
> }
>
> #endif /* CONFIG_SMP || CONFIG_HOTPLUG_CPU */
> +
> +#ifdef CONFIG_STOP_MACHINE_NMI
> +
> +void arch_send_self_nmi(void);
> +bool noinstr stop_machine_nmi_handler(void);
> +
> +#else
> +static inline bool stop_machine_nmi_handler(void) { return false; }
> +#endif /* CONFIG_STOP_MACHINE_NMI */
> +
> #endif /* _LINUX_STOP_MACHINE */
> diff --git a/kernel/stop_machine.c b/kernel/stop_machine.c
> index 8dcd5be67b85..ad01b7c5266c 100644
> --- a/kernel/stop_machine.c
> +++ b/kernel/stop_machine.c
> @@ -23,6 +23,7 @@
> #include <linux/atomic.h>
> #include <linux/nmi.h>
> #include <linux/sched/wake_q.h>
> +#include <linux/delay.h>
>
> /*
> * Structure to determine completion condition and record errors. May
> @@ -175,6 +176,15 @@ struct multi_stop_data {
>
> enum multi_stop_state state;
> atomic_t thread_ack;
> +
> +#ifdef CONFIG_STOP_MACHINE_NMI

I hate this ifdeffery.

> + /* Used in the NMI stop_machine variant */
> + bool use_nmi;
> + /* A separate function type to highlight noinstr requirement */

What?

This is the function that stop_machine_nmi calls. Pls keep commenting to the
point and at minimum.

> + cpu_stop_nmisafe_fn_t nmisafe_fn;
> + /* cpumask of CPUs on which to raise an NMI */

Kinda obvious from the name, right?

> + cpumask_var_t nmi_cpus;
> +#endif
> };
>
> static void set_state(struct multi_stop_data *msdata,
> @@ -198,6 +208,8 @@ notrace void __weak stop_machine_yield(const struct cpumask *cpumask)
> cpu_relax();
> }
>
> +static int multi_stop_run(struct multi_stop_data *msdata);

Get rid of this.

> +
> /* This is the cpu_stop function which stops the CPU. */
> static int multi_cpu_stop(void *data)
> {
> @@ -236,7 +248,7 @@ static int multi_cpu_stop(void *data)
> break;
> case MULTI_STOP_RUN:
> if (is_active)
> - err = msdata->fn(msdata->data);
> + err = multi_stop_run(msdata);
> break;
> default:
> break;
> @@ -713,3 +725,88 @@ int stop_machine_from_inactive_cpu(cpu_stop_fn_t fn, void *data,
> mutex_unlock(&stop_cpus_mutex);
> return ret ?: done.ret;
> }
> +
> +#ifdef CONFIG_STOP_MACHINE_NMI
> +
> +struct nmi_stop {

If you already have "multi_stop_data" then this is "nmi_stop_data".

Please try not to come up with weird naming willy nilly.

> + struct multi_stop_data *data;
> + int ret;
> +};
> +
> +static DEFINE_PER_CPU(struct nmi_stop, nmi_stop);

static DEFINE_PER_CPU(struct nmi_stop_data, nmi_data);

> +
> +/*
> + * Instrumentation may trigger nested exceptions such as #INT3, #DB,
> + * or #PF. IRET from those would re-enable NMIs, which opposes the goal
> + * of this NMI stop-machine facility.
> + */
> +bool noinstr stop_machine_nmi_handler(void)

stop_machine_nmi()

> +{
> + struct multi_stop_data *msdata = raw_cpu_read(nmi_stop.data);
> + unsigned int cpu = raw_smp_processor_id();
> + int ret;
> +
> + if (!msdata || !arch_test_and_clear_bit(cpu, cpumask_bits(msdata->nmi_cpus)))
> + return false;
> +
> + /*
> + * The indirect call to @nmisafe_fn() is indistinguishable at
> + * post-compilation. Temporarily enabling instrumentation avoids
> + * objtool false positives.
> + */
> + instrumentation_begin();
> + ret = msdata->nmisafe_fn(msdata->data);
> + instrumentation_end();

This is just silly. We should fix objtool here.

Peter?

> + raw_cpu_write(nmi_stop.ret, ret);
> + raw_cpu_write(nmi_stop.data, NULL);
> +
> + return true;
> +}
> +
> +static bool wait_for_nmi_handler(void)
> +{
> + /* Conservative timeout */
> + unsigned long timeout = USEC_PER_SEC;
> +
> + /* The handler clears up at the end */
> + while (this_cpu_read(nmi_stop.data) && timeout--)
> + udelay(1);
> +
> + return !this_cpu_read(nmi_stop.data);
> +}
> +
> +static int nmi_stop_run(struct multi_stop_data *msdata)
> +{
> + /*
> + * Save per-CPU state accessible from NMI context and raise a
> + * self-NMI to execute the stop function from the NMI handler
> + */
> + this_cpu_write(nmi_stop.data, msdata);
> + arch_send_self_nmi();
> +
> + /*
> + * Ensure the handler went through before reading the result.
> + * Otherwise, make no stale state left behind.
> + */
> + if (!wait_for_nmi_handler()) {
> + this_cpu_write(nmi_stop.data, NULL);
> + return -ETIMEDOUT;
> + }
> +
> + return this_cpu_read(nmi_stop.ret);
> +}
> +
> +static int multi_stop_run(struct multi_stop_data *msdata)

Call it what it is:

stop_machine_callback()

or

stop_machine_fn()

or so. Naming is very important.

> +{
> + return msdata->use_nmi ? nmi_stop_run(msdata) : msdata->fn(msdata->data);
> +}
> +
> +#else
> +
> +static int multi_stop_run(struct multi_stop_data *msdata)
> +{
> + return msdata->fn(msdata->data);
> +}
> +
> +#endif /* CONFIG_STOP_MACHINE_NMI */
> --
> 2.53.0
>

--
Regards/Gruss,
Boris.

https://people.kernel.org/tglx/notes-about-netiquette