Linux VBS (LVBS) uses Hyper-V's Virtual Secure Mode to run a small
trusted kernel in VTL1 alongside the regular VTL0 kernel, so that
security-sensitive state (e.g. hypervisor-enforced code integrity,
credential isolation) can live behind a higher-privilege boundary
that VTL0 compromise cannot cross. Bringing that up from Linux
requires the VTL0 kernel to drive the VSM setup itself.

Add drivers/hv/hv_vsm_boot.c as the entry point for that sequence.
This first step handles partition-level VTL1 enable only:

  - Probe VSM / VP-register privileges and SynIC availability before
    doing anything.
  - Pin init to the VTL0 boot CPU so VTL1 comes up on the same CPU
    (later patches rely on this).
  - Read HV_REGISTER_VSM_PARTITION_STATUS, and if VTL1 is not already
    enabled, issue HVCALL_ENABLE_PARTITION_VTL with MBEC and confirm
    by re-reading the register.

Signed-off-by: Thara Gopinath <[email protected]>
---
 drivers/hv/Makefile      |   4 +-
 drivers/hv/hv_vsm_boot.c | 178 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 180 insertions(+), 2 deletions(-)
 create mode 100644 drivers/hv/hv_vsm_boot.c

diff --git a/drivers/hv/Makefile b/drivers/hv/Makefile
index 880c570832381..563ebc36d2700 100644
--- a/drivers/hv/Makefile
+++ b/drivers/hv/Makefile
@@ -22,7 +22,7 @@ mshv_vtl-y := mshv_vtl_main.o
 # Code that must be built-in
 obj-$(CONFIG_HYPERV) += hv_common.o
 obj-$(subst m,y,$(CONFIG_MSHV_ROOT)) += hv_proc.o
-ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL),)
+ifneq ($(CONFIG_MSHV_ROOT)$(CONFIG_MSHV_VTL)$(CONFIG_HYPERV_VSM),)
        obj-y += mshv_common.o
 endif
-obj-$(subst m,y,$(CONFIG_HYPERV_VSM)) += hv_vsm_securekernel.o
+obj-$(subst m,y,$(CONFIG_HYPERV_VSM)) += hv_vsm_securekernel.o hv_vsm_boot.o
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
new file mode 100644
index 0000000000000..99e4dc8695837
--- /dev/null
+++ b/drivers/hv/hv_vsm_boot.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VSM boot framework that enables VTL1, loads secure kernel
+ * and boots VTL1.
+ *
+ * Copyright (c) 2023-2025, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <[email protected]>
+ *
+ */
+
+#define pr_fmt(fmt) "vsm: " fmt
+
+#include <linux/hyperv.h>
+#include <linux/cpumask.h>
+#include <asm/mshyperv.h>
+#include "mshv.h"
+
+#define HV_VTL1_ENABLE_BIT     BIT(1)
+
+static int hv_vsm_get_register(u32 reg_name, u64 *result)
+{
+       struct hv_register_assoc reg = {
+               .name = reg_name,
+       };
+       union hv_input_vtl input_vtl = {
+               .as_uint8 = 0,
+       };
+       int ret;
+
+       ret = hv_call_get_vp_registers(HV_VP_INDEX_SELF,
+                                      HV_PARTITION_ID_SELF,
+                                      1, input_vtl, &reg);
+       if (ret)
+               return ret;
+
+       *result = reg.value.reg64;
+       return 0;
+}
+
+static int __init hv_vsm_enable_partition_vtl(void)
+{
+       u64 status = 0;
+       unsigned long flags;
+       struct hv_input_enable_partition_vtl *hvin = NULL;
+
+       local_irq_save(flags);
+
+       hvin = *this_cpu_ptr(hyperv_pcpu_input_arg);
+       memset(hvin, 0, sizeof(*hvin));
+
+       hvin->partition_id = HV_PARTITION_ID_SELF;
+       hvin->target_vtl.as_uint8 = 1;
+       hvin->flags.enable_mbec = 1;
+
+       status = hv_do_hypercall(HVCALL_ENABLE_PARTITION_VTL, hvin, NULL);
+       if (hv_result(status))
+               pr_err("Enable Partition VTL failed. status=0x%x\n",
+                      hv_result(status));
+
+       local_irq_restore(flags);
+
+       return hv_result(status);
+}
+
+static int __init hv_vsm_get_partition_status(u16 *enabled_vtl_set, u8 
*max_vtl,
+                                             u16 *mbec_enabled_vtl_set)
+{
+       u64 result;
+       int ret;
+       union hv_register_vsm_partition_status vsm_partition_status = { 0 };
+
+       ret = hv_vsm_get_register(HV_REGISTER_VSM_PARTITION_STATUS, &result);
+       if (ret)
+               return ret;
+
+       vsm_partition_status = (union hv_register_vsm_partition_status)result;
+       *enabled_vtl_set = vsm_partition_status.enabled_vtl_set;
+       *max_vtl = vsm_partition_status.max_vtl;
+       *mbec_enabled_vtl_set = vsm_partition_status.mbec_enabled_vtl_set;
+       return 0;
+}
+
+static int __init hv_vsm_bootstrap_vtl(void)
+{
+       u16 partition_enabled_vtl_set = 0, partition_mbec_enabled_vtl_set = 0;
+       u8 partition_max_vtl;
+       int ret;
+
+       /* Check and enable VTL1 at the partition level */
+       ret = hv_vsm_get_partition_status(&partition_enabled_vtl_set, 
&partition_max_vtl,
+                                         &partition_mbec_enabled_vtl_set);
+       if (ret)
+               return ret;
+
+       if (partition_max_vtl < HV_VTL_SECURE) {
+               pr_err("VTL1 is not supported by the partition\n");
+               return -EINVAL;
+       }
+
+       if (partition_enabled_vtl_set & HV_VTL1_ENABLE_BIT) {
+               pr_info("Partition VTL1 is already enabled\n");
+       } else {
+               ret = hv_vsm_enable_partition_vtl();
+               if (ret) {
+                       pr_err("Enabling Partition VTL1 failed with status 
0x%x\n",
+                              ret);
+                       return -EINVAL;
+               }
+               ret = hv_vsm_get_partition_status(&partition_enabled_vtl_set, 
&partition_max_vtl,
+                                                 
&partition_mbec_enabled_vtl_set);
+               if (ret)
+                       return ret;
+               if (!(partition_enabled_vtl_set & HV_VTL1_ENABLE_BIT)) {
+                       pr_err("Tried Enabling Partition VTL 1 and still 
failed\n");
+                       return -EINVAL;
+               }
+               if (!partition_mbec_enabled_vtl_set) {
+                       pr_err("Tried Enabling Partition MBEC and failed\n");
+                       return -EINVAL;
+               }
+       }
+       return 0;
+}
+
+static bool __init vsm_arch_has_vsm_access(void)
+{
+       if (!(ms_hyperv.features & HV_MSR_SYNIC_AVAILABLE))
+               return false;
+       if (!(ms_hyperv.priv_high & HV_ACCESS_VSM))
+               return false;
+       if (!(ms_hyperv.priv_high & HV_ACCESS_VP_REGS))
+               return false;
+       return true;
+}
+
+static int __init hv_vsm_boot_init(void)
+{
+       cpumask_var_t mask;
+       unsigned int boot_cpu;
+       int ret;
+
+       if (!vsm_arch_has_vsm_access())
+               return 0;
+
+       /*
+        * Copy the current cpu mask and pin rest of the running code to boot 
cpu.
+        * Important since we want boot cpu of VTL0 to be the boot cpu for VTL1.
+        * ToDo: Check if copying and restoring current->cpus_mask is enough
+        * ToDo: Verify the assumption that cpumask_first(cpu_online_mask) is
+        * the boot cpu
+        */
+       if (!alloc_cpumask_var(&mask, GFP_KERNEL))
+               panic("Could not allocate cpumask");
+
+       cpumask_copy(mask, &current->cpus_mask);
+       boot_cpu = cpumask_first(cpu_online_mask);
+       set_cpus_allowed_ptr(current, cpumask_of(boot_cpu));
+
+       ret = hv_vsm_bootstrap_vtl();
+       /*
+        * At this point VTL0 has already advertised VSM support to the
+        * bootloader/firmware via the Hyper-V OsLoaderIndications EFI
+        * variable (see the x86-stub change). That signals the platform
+        * that a trusted VTL1 will be brought up. If we fail to actually
+        * set VTL1 up here, the partition is left in a state where an
+        * attacker could race to configure VTL1 themselves and gain a
+        * higher-privilege foothold than VTL0. Panic rather than continue
+        * running with that exposure.
+        */
+       if (ret)
+               panic("VTL1 boot failure caused kernel panic; consult log for 
more details.\n");
+
+       set_cpus_allowed_ptr(current, mask);
+       free_cpumask_var(mask);
+       return ret;
+}
+device_initcall(hv_vsm_boot_init);
-- 
2.34.1


Reply via email to