[PATCH RFC 5/8] x86/microcode: Decouple minimum revision check from late loading

From: Chang S. Bae

Date: Tue Sep 01 2026 - 19:45:44 EST


The microcode minimum revision check was originally introduced to express
dependencies involving OS-visible changes, thus it has been limited to
the late-load path.

In fact this can serve as a general guard against unsafe microcode
loading. Make the check independent of the loading path in preparation
for applying it to the early path as well.

For now, reject early loading if enforced. Taint the kernel on early
loading because the early path does not perform the check yet.

Signed-off-by: Chang S. Bae <chang.seok.bae@xxxxxxxxx>
---
Note for the review:
The AMD loader does not have the minrev checker anywhere (yet). So,
force_minrev is effectively dis_ucode_ldr which may appear excessive.
Without enforcement though, it is going to taint the kernel on every
early loading then. This side-effect should be reviewed.
---
.../admin-guide/kernel-parameters.txt | 4 ++--
arch/x86/Kconfig | 19 +++++++++----------
arch/x86/kernel/cpu/microcode/amd.c | 8 +++++++-
arch/x86/kernel/cpu/microcode/core.c | 13 ++++++++++---
arch/x86/kernel/cpu/microcode/intel.c | 17 ++++++++++++-----
arch/x86/kernel/cpu/microcode/internal.h | 1 +
6 files changed, 41 insertions(+), 21 deletions(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index cde092017cd8..b5f7a8142f58 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -4073,8 +4073,8 @@ Kernel parameters

force_minrev[=<bool>]:
Enable or disable microcode minimal revision enforcement
- for the runtime microcode loader according to <bool>. If
- <bool> is not given, enable the enforcement.
+ for the microcode loader according to <bool>. If <bool>
+ is not given, enable the enforcement.

mini2440= [ARM,HW,KNL]
Format:[0..2][b][c][t]
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 15fd9ec5ecac..6c503004775e 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1338,18 +1338,17 @@ config MICROCODE_LATE_LOADING
minimal revision check. This minimal revision check can be enforced on
the kernel command line with "microcode=force_minrev".

-config MICROCODE_LATE_FORCE_MINREV
- bool "Enforce late microcode loading minimal revision check"
+config MICROCODE_FORCE_MINREV
+ bool "Enforce microcode loading minimal revision check"
default n
- depends on MICROCODE_LATE_LOADING
+ depends on MICROCODE
help
- To prevent that users load microcode late which modifies already
- in use features, newer microcode patches have a minimum revision field
- in the microcode header, which tells the kernel which minimum
- revision must be active in the CPU to safely load that new microcode
- late into the running system. If disabled the check will not
- be enforced but the kernel will be tainted when the minimal
- revision check fails.
+ To prevent that users load microcode which modifies already in use
+ features, newer microcode patches have a minimum revision field in the
+ microcode header, which tells the kernel which minimum revision must
+ be active in the CPU to safely load that new microcode into the system.
+ If disabled the check will not be enforced but the kernel will be
+ tainted when the minimal revision check fails.

This minimal revision check can also be controlled via the
"microcode=force_minrev" parameter on the kernel command line.
diff --git a/arch/x86/kernel/cpu/microcode/amd.c b/arch/x86/kernel/cpu/microcode/amd.c
index 6cdc410e7547..0625d5e8eb7e 100644
--- a/arch/x86/kernel/cpu/microcode/amd.c
+++ b/arch/x86/kernel/cpu/microcode/amd.c
@@ -800,6 +800,11 @@ void __init load_ucode_amd_bsp(struct early_load_data *ed, unsigned int cpuid_1_
/* Needed in load_microcode_amd() */
ucode_cpu_info[0].cpu_sig.sig = cpuid_1_eax;

+ if (force_minrev) {
+ pr_warn_once("No early load: minimum revision check is not implemented.\n");
+ return;
+ }
+
if (!find_blobs_in_containers(&cp))
return;

@@ -1202,7 +1207,8 @@ static int __init save_microcode_in_initrd(void)
enum ucode_state ret;
struct cpio_data cp;

- if (microcode_loader_disabled() || c->x86_vendor != X86_VENDOR_AMD || c->x86 < 0x10)
+ if (microcode_loader_disabled() || c->x86_vendor != X86_VENDOR_AMD || c->x86 < 0x10 ||
+ force_minrev)
return 0;

cpuid_1_eax = native_cpuid_eax(1);
diff --git a/arch/x86/kernel/cpu/microcode/core.c b/arch/x86/kernel/cpu/microcode/core.c
index ed1eb6b753d6..7d6caf191795 100644
--- a/arch/x86/kernel/cpu/microcode/core.c
+++ b/arch/x86/kernel/cpu/microcode/core.c
@@ -46,7 +46,7 @@
static struct microcode_ops *microcode_ops;
static bool dis_ucode_ldr;

-bool force_minrev = IS_ENABLED(CONFIG_MICROCODE_LATE_FORCE_MINREV);
+bool force_minrev = IS_ENABLED(CONFIG_MICROCODE_FORCE_MINREV);

/*
* Those below should be behind CONFIG_MICROCODE_DBG ifdeffery but in
@@ -603,7 +603,7 @@ static int load_late_stop_cpus(bool is_safe)

if (!is_safe) {
pr_err("Late microcode loading without minimal revision check.\n");
- pr_err("You should switch to early loading, if possible.\n");
+ pr_err("You should update microcode incrementally.\n");
}

/*
@@ -912,9 +912,16 @@ static int __init microcode_init(void)

pr_info_once("Current revision: 0x%08x\n", (early_data.new_rev ?: early_data.old_rev));

- if (early_data.new_rev)
+ if (early_data.new_rev) {
pr_info_once("Updated early from: 0x%08x\n", early_data.old_rev);

+ if (!early_data.is_safe) {
+ pr_err("Early microcode loading without minimal revision check.\n");
+ pr_err("You should update microcode incrementally.\n");
+ add_taint(TAINT_CPU_OUT_OF_SPEC, LOCKDEP_STILL_OK);
+ }
+ }
+
microcode_fdev = faux_device_create("microcode", NULL, NULL);
if (!microcode_fdev)
return -ENODEV;
diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c
index c502138fd8e9..3247c619eaa1 100644
--- a/arch/x86/kernel/cpu/microcode/intel.c
+++ b/arch/x86/kernel/cpu/microcode/intel.c
@@ -786,7 +786,15 @@ void __init load_ucode_intel_bsp(struct early_load_data *ed)
uci.mc = get_microcode_blob(&uci, false);
ed->old_rev = uci.cpu_sig.rev;

- if (uci.mc && apply_microcode_early(&uci) == UCODE_UPDATED) {
+ if (!uci.mc)
+ return;
+
+ if (force_minrev) {
+ pr_warn_once("No early load: minimum revision check is not implemented.\n");
+ return;
+ }
+
+ if (apply_microcode_early(&uci) == UCODE_UPDATED) {
ucode_patch_va = UCODE_BSP_LOADED;
ed->new_rev = uci.cpu_sig.rev;
}
@@ -842,9 +850,8 @@ static bool ucode_validate_minrev(struct microcode_header_intel *mc_header)
int cur_rev = boot_cpu_data.microcode;

/*
- * When late-loading, ensure the header declares a minimum revision
- * required to perform a late-load. The previously reserved field
- * is 0 in older microcode blobs.
+ * Ensure the header declares a minimum revision required to perform a
+ * load. The previously reserved field is 0 in older microcode blobs.
*/
if (!mc_header->min_req_ver) {
pr_info("Unsafe microcode update: Microcode header does not specify a required min version\n");
@@ -857,7 +864,7 @@ static bool ucode_validate_minrev(struct microcode_header_intel *mc_header)
*/
if (cur_rev < mc_header->min_req_ver) {
pr_info("Unsafe microcode update: Current revision 0x%x too old\n", cur_rev);
- pr_info("Current should be at 0x%x or higher. Use early loading instead\n", mc_header->min_req_ver);
+ pr_info("Current should be at 0x%x or higher. Update incrementally.\n", mc_header->min_req_ver);
return false;
}
return true;
diff --git a/arch/x86/kernel/cpu/microcode/internal.h b/arch/x86/kernel/cpu/microcode/internal.h
index a10b547eda1e..1b35f9099580 100644
--- a/arch/x86/kernel/cpu/microcode/internal.h
+++ b/arch/x86/kernel/cpu/microcode/internal.h
@@ -42,6 +42,7 @@ struct microcode_ops {
struct early_load_data {
u32 old_rev;
u32 new_rev;
+ bool is_safe;
};

extern struct early_load_data early_data;
--
2.53.0