diff mbox series

[v2,4/4] x86/microcode: Prevent attempting updates if DIS_MCU_LOAD is set

Message ID 20230605170817.9913-5-alejandro.vallejo@cloud.com (mailing list archive)
State Superseded
Headers show
Series Prevent attempting updates known to fail | expand

Commit Message

Alejandro Vallejo June 5, 2023, 5:08 p.m. UTC
If IA32_MSR_MCU_CONTROL exists then it's possible a CPU may be unable to
perform microcode updates. This is controlled through the DIS_MCU_LOAD bit
and is intended for baremetal clouds where the owner may not trust the
tenant to choose the microcode version in use. This patch makes sure we
only expose the microcode loading interface when it can be actually used,
while also allowing reads of current microcode versions.

Patch summary:
 * Read CPUID leaf 7d0 early so DIS_MCU_LOAD can be checked.
 * Hide microcode loading handlers if DIS_MCU_LOAD is set except for
   collect_cpu_info()
 * Update microcode_update_one() so APs can read their
   microcode version even if DIS_MCU_LOAD is set.

Signed-off-by: Alejandro Vallejo <alejandro.vallejo@cloud.com>

---
v2:
  * Moved check from apply time to init time.
---
 xen/arch/x86/cpu/microcode/core.c     | 31 +++++++++++++++++++++++++--
 xen/arch/x86/include/asm/cpufeature.h |  1 +
 xen/arch/x86/include/asm/msr-index.h  |  5 +++++
 3 files changed, 35 insertions(+), 2 deletions(-)

Comments

Andrew Cooper June 12, 2023, 6:54 p.m. UTC | #1
On 05/06/2023 6:08 pm, Alejandro Vallejo wrote:
> diff --git a/xen/arch/x86/cpu/microcode/core.c b/xen/arch/x86/cpu/microcode/core.c
> index 4f60d96d98..a4c123118b 100644
> --- a/xen/arch/x86/cpu/microcode/core.c
> +++ b/xen/arch/x86/cpu/microcode/core.c
> @@ -871,6 +885,15 @@ int __init early_microcode_init(unsigned long *module_map,
>           * present.
>           */
>          ucode_ops = intel_ucode_ops;
> +
> +        /*
> +         * In the case where microcode updates are blocked by the
> +         * DIS_MCU_LOAD bit we can still read the microcode version even if
> +         * we can't change it.
> +         */
> +        if ( !this_cpu_can_install_update() )
> +            ucode_ops = (struct microcode_ops){ .collect_cpu_info =
> +                                    intel_ucode_ops.collect_cpu_info };

I don't see how this (the logic in this_cpu_can_install_update()) can
work, as ...

>          break;
>      }
>  
> @@ -900,6 +923,10 @@ int __init early_microcode_init(unsigned long *module_map,
>      if ( ucode_mod.mod_end || ucode_blob.size )
>          rc = early_microcode_update_cpu();
>  
> +    /*
> +     * We just updated microcode so we must reload the boot_cpu_data bits
> +     * we read before because they might be stale after the updata.
> +     */
>      early_read_cpuid_7d0();
>  
>      /*

... MSR_ARCH_CAPS is read out-of-context down here.

In hindsight, I think swapping patch 2 and 3 might be wise.  The rev ==
~0 case doesn't need any of the cpu_has_* shuffling, and it already
starts to build up the if/else chain of cases where we decide to clobber
the apply_microcode() hook.

The call to this_cpu_can_install_update() should be lower down.  In
principle it's not Intel-specific.

~Andrew
Jan Beulich June 13, 2023, 6:57 a.m. UTC | #2
On 05.06.2023 19:08, Alejandro Vallejo wrote:
> --- a/xen/arch/x86/cpu/microcode/core.c
> +++ b/xen/arch/x86/cpu/microcode/core.c
> @@ -749,11 +749,12 @@ __initcall(microcode_init);
>  /* Load a cached update to current cpu */
>  int microcode_update_one(void)
>  {
> +    if ( ucode_ops.collect_cpu_info )
> +        alternative_vcall(ucode_ops.collect_cpu_info);
> +
>      if ( !ucode_ops.apply_microcode )
>          return -EOPNOTSUPP;
>  
> -    alternative_vcall(ucode_ops.collect_cpu_info);
> -
>      return microcode_update_cpu(NULL);
>  }

This adjustment (and related logic below) doesn't really fit the purpose
that the title states. I wonder if the two things wouldn't better be
split.

> @@ -849,12 +850,25 @@ static void __init early_read_cpuid_7d0(void)
>              = cpuid_count_edx(7, 0);
>  }
>  
> +static bool __init this_cpu_can_install_update(void)
> +{
> +    uint64_t mcu_ctrl;
> +
> +    if ( !cpu_has_mcu_ctrl )
> +        return true;
> +
> +    rdmsrl(MSR_MCU_CONTROL, mcu_ctrl);
> +    return !(mcu_ctrl & MCU_CONTROL_DIS_MCU_LOAD);
> +}

As Andrew says, in principle AMD could have a means as well. Surely that
would be a different one, so I wonder if this shouldn't be a new hook.

> @@ -871,6 +885,15 @@ int __init early_microcode_init(unsigned long *module_map,
>           * present.
>           */
>          ucode_ops = intel_ucode_ops;
> +
> +        /*
> +         * In the case where microcode updates are blocked by the
> +         * DIS_MCU_LOAD bit we can still read the microcode version even if
> +         * we can't change it.
> +         */
> +        if ( !this_cpu_can_install_update() )
> +            ucode_ops = (struct microcode_ops){ .collect_cpu_info =
> +                                    intel_ucode_ops.collect_cpu_info };

Similarly I'm not happy to see a direct reference to some vendor specific
field. I think it wants to be the hook to return such an override struct.

Jan
Alejandro Vallejo June 13, 2023, 1:01 p.m. UTC | #3
On Mon, Jun 12, 2023 at 07:54:00PM +0100, Andrew Cooper wrote:
> On 05/06/2023 6:08 pm, Alejandro Vallejo wrote:
> > diff --git a/xen/arch/x86/cpu/microcode/core.c b/xen/arch/x86/cpu/microcode/core.c
> > index 4f60d96d98..a4c123118b 100644
> > --- a/xen/arch/x86/cpu/microcode/core.c
> > +++ b/xen/arch/x86/cpu/microcode/core.c
> > @@ -871,6 +885,15 @@ int __init early_microcode_init(unsigned long *module_map,
> >           * present.
> >           */
> >          ucode_ops = intel_ucode_ops;
> > +
> > +        /*
> > +         * In the case where microcode updates are blocked by the
> > +         * DIS_MCU_LOAD bit we can still read the microcode version even if
> > +         * we can't change it.
> > +         */
> > +        if ( !this_cpu_can_install_update() )
> > +            ucode_ops = (struct microcode_ops){ .collect_cpu_info =
> > +                                    intel_ucode_ops.collect_cpu_info };
> 
> I don't see how this (the logic in this_cpu_can_install_update()) can
> work, as ...
> 
> >          break;
> >      }
> >  
> > @@ -900,6 +923,10 @@ int __init early_microcode_init(unsigned long *module_map,
> >      if ( ucode_mod.mod_end || ucode_blob.size )
> >          rc = early_microcode_update_cpu();
> >  
> > +    /*
> > +     * We just updated microcode so we must reload the boot_cpu_data bits
> > +     * we read before because they might be stale after the updata.
> > +     */
> >      early_read_cpuid_7d0();
> >  
> >      /*
> 
> ... MSR_ARCH_CAPS is read out-of-context down here.
Seeing how the minimal CPU state is read in early_cpu_init() I'll stash the
read to MSR_ARCH_CAPS there too. Then it's a matter of reloading
potentially changing leafs/MSRs after the update, which is a lot clearer
rather than adding reads/writes ad-hoc elsewhere.

Alejandro
Alejandro Vallejo June 13, 2023, 4:42 p.m. UTC | #4
On Tue, Jun 13, 2023 at 08:57:06AM +0200, Jan Beulich wrote:
> On 05.06.2023 19:08, Alejandro Vallejo wrote:
> > --- a/xen/arch/x86/cpu/microcode/core.c
> > +++ b/xen/arch/x86/cpu/microcode/core.c
> > @@ -749,11 +749,12 @@ __initcall(microcode_init);
> >  /* Load a cached update to current cpu */
> >  int microcode_update_one(void)
> >  {
> > +    if ( ucode_ops.collect_cpu_info )
> > +        alternative_vcall(ucode_ops.collect_cpu_info);
> > +
> >      if ( !ucode_ops.apply_microcode )
> >          return -EOPNOTSUPP;
> >  
> > -    alternative_vcall(ucode_ops.collect_cpu_info);
> > -
> >      return microcode_update_cpu(NULL);
> >  }
> 
> This adjustment (and related logic below) doesn't really fit the purpose
> that the title states. I wonder if the two things wouldn't better be
> split.
Well, before this patch collect_cpu_info() and apply_microcode() were both
set or cleared together , whereas after this patch that's no longer the
case (hence the change). I can submit it standalone patch earlier in v3,
seeing as it does no harm. The commit message could also do with better
wording.

> 
> > @@ -849,12 +850,25 @@ static void __init early_read_cpuid_7d0(void)
> >              = cpuid_count_edx(7, 0);
> >  }
> >  
> > +static bool __init this_cpu_can_install_update(void)
> > +{
> > +    uint64_t mcu_ctrl;
> > +
> > +    if ( !cpu_has_mcu_ctrl )
> > +        return true;
> > +
> > +    rdmsrl(MSR_MCU_CONTROL, mcu_ctrl);
> > +    return !(mcu_ctrl & MCU_CONTROL_DIS_MCU_LOAD);
> > +}
> 
> As Andrew says, in principle AMD could have a means as well. Surely that
> would be a different one, so I wonder if this shouldn't be a new hook.
> 
> > @@ -871,6 +885,15 @@ int __init early_microcode_init(unsigned long *module_map,
> >           * present.
> >           */
> >          ucode_ops = intel_ucode_ops;
> > +
> > +        /*
> > +         * In the case where microcode updates are blocked by the
> > +         * DIS_MCU_LOAD bit we can still read the microcode version even if
> > +         * we can't change it.
> > +         */
> > +        if ( !this_cpu_can_install_update() )
> > +            ucode_ops = (struct microcode_ops){ .collect_cpu_info =
> > +                                    intel_ucode_ops.collect_cpu_info };
> 
> Similarly I'm not happy to see a direct reference to some vendor specific
> field. I think it wants to be the hook to return such an override struct.
> 
> Jan
I'm moving things around a little in v3. We'll have accessors for both AMD
and Intel that provide the right thing, encapsulating vendor-specifics
(including family checks) inside ${VENDOR}.c

Alejandro
diff mbox series

Patch

diff --git a/xen/arch/x86/cpu/microcode/core.c b/xen/arch/x86/cpu/microcode/core.c
index 4f60d96d98..a4c123118b 100644
--- a/xen/arch/x86/cpu/microcode/core.c
+++ b/xen/arch/x86/cpu/microcode/core.c
@@ -749,11 +749,12 @@  __initcall(microcode_init);
 /* Load a cached update to current cpu */
 int microcode_update_one(void)
 {
+    if ( ucode_ops.collect_cpu_info )
+        alternative_vcall(ucode_ops.collect_cpu_info);
+
     if ( !ucode_ops.apply_microcode )
         return -EOPNOTSUPP;
 
-    alternative_vcall(ucode_ops.collect_cpu_info);
-
     return microcode_update_cpu(NULL);
 }
 
@@ -849,12 +850,25 @@  static void __init early_read_cpuid_7d0(void)
             = cpuid_count_edx(7, 0);
 }
 
+static bool __init this_cpu_can_install_update(void)
+{
+    uint64_t mcu_ctrl;
+
+    if ( !cpu_has_mcu_ctrl )
+        return true;
+
+    rdmsrl(MSR_MCU_CONTROL, mcu_ctrl);
+    return !(mcu_ctrl & MCU_CONTROL_DIS_MCU_LOAD);
+}
+
 int __init early_microcode_init(unsigned long *module_map,
                                 const struct multiboot_info *mbi)
 {
     const struct cpuinfo_x86 *c = &boot_cpu_data;
     int rc = 0;
 
+    early_read_cpuid_7d0();
+
     switch ( c->x86_vendor )
     {
     case X86_VENDOR_AMD:
@@ -871,6 +885,15 @@  int __init early_microcode_init(unsigned long *module_map,
          * present.
          */
         ucode_ops = intel_ucode_ops;
+
+        /*
+         * In the case where microcode updates are blocked by the
+         * DIS_MCU_LOAD bit we can still read the microcode version even if
+         * we can't change it.
+         */
+        if ( !this_cpu_can_install_update() )
+            ucode_ops = (struct microcode_ops){ .collect_cpu_info =
+                                    intel_ucode_ops.collect_cpu_info };
         break;
     }
 
@@ -900,6 +923,10 @@  int __init early_microcode_init(unsigned long *module_map,
     if ( ucode_mod.mod_end || ucode_blob.size )
         rc = early_microcode_update_cpu();
 
+    /*
+     * We just updated microcode so we must reload the boot_cpu_data bits
+     * we read before because they might be stale after the updata.
+     */
     early_read_cpuid_7d0();
 
     /*
diff --git a/xen/arch/x86/include/asm/cpufeature.h b/xen/arch/x86/include/asm/cpufeature.h
index ace31e3b1f..0118171d7e 100644
--- a/xen/arch/x86/include/asm/cpufeature.h
+++ b/xen/arch/x86/include/asm/cpufeature.h
@@ -192,6 +192,7 @@  static inline bool boot_cpu_has(unsigned int feat)
 #define cpu_has_if_pschange_mc_no boot_cpu_has(X86_FEATURE_IF_PSCHANGE_MC_NO)
 #define cpu_has_tsx_ctrl        boot_cpu_has(X86_FEATURE_TSX_CTRL)
 #define cpu_has_taa_no          boot_cpu_has(X86_FEATURE_TAA_NO)
+#define cpu_has_mcu_ctrl        boot_cpu_has(X86_FEATURE_MCU_CTRL)
 #define cpu_has_fb_clear        boot_cpu_has(X86_FEATURE_FB_CLEAR)
 
 /* Synthesized. */
diff --git a/xen/arch/x86/include/asm/msr-index.h b/xen/arch/x86/include/asm/msr-index.h
index 2749e433d2..5c1350b5f9 100644
--- a/xen/arch/x86/include/asm/msr-index.h
+++ b/xen/arch/x86/include/asm/msr-index.h
@@ -165,6 +165,11 @@ 
 #define  PASID_PASID_MASK                   0x000fffff
 #define  PASID_VALID                        (_AC(1, ULL) << 31)
 
+#define MSR_MCU_CONTROL                     0x00001406
+#define  MCU_CONTROL_LOCK                   (_AC(1, ULL) <<  0)
+#define  MCU_CONTROL_DIS_MCU_LOAD           (_AC(1, ULL) <<  1)
+#define  MCU_CONTROL_EN_SMM_BYPASS          (_AC(1, ULL) <<  2)
+
 #define MSR_UARCH_MISC_CTRL                 0x00001b01
 #define  UARCH_CTRL_DOITM                   (_AC(1, ULL) <<  0)