LVBS bring-up requires loading a secure kernel image into VTL1 before
starting it. Add the VTL0-side loader that stages the image in the
memory region reserved by hv_vsm_securekernel, in preparation for the
VTL1 bring-up.

The image is a 64-bit ELF fetched via request_firmware("vsm_sk"). It
is expected to ship inside the signed UKI/initramfs so it is
authenticated end-to-end via Secure Boot before the loader consumes
it; sourcing it from an unauthenticated location would break the LVBS
trust model.

The loader validates the ELF header, stages the PT_LOAD segments into
the reserved region and records the entry point as a physical address
for use at VTL1 start time.

If VSM support has been advertised to the hypervisor but no secure
kernel region was reserved on the command line, panic: LVBS bring-up
is committed at this point and there is no safe way to continue.

Signed-off-by: Stanislav Kinsburskii <[email protected]>
Signed-off-by: Thara Gopinath <[email protected]>
---
 drivers/hv/hv_vsm.h      |  17 ++++
 drivers/hv/hv_vsm_boot.c | 201 ++++++++++++++++++++++++++++++++++++++-
 include/hyperv/vsm.h     |  21 ++++
 3 files changed, 238 insertions(+), 1 deletion(-)
 create mode 100644 drivers/hv/hv_vsm.h
 create mode 100644 include/hyperv/vsm.h

diff --git a/drivers/hv/hv_vsm.h b/drivers/hv/hv_vsm.h
new file mode 100644
index 0000000000000..88f099f88eeb4
--- /dev/null
+++ b/drivers/hv/hv_vsm.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2023-2026, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <[email protected]>
+ *
+ */
+
+#ifndef _HV_VSM_H
+#define _HV_VSM_H
+
+#include <linux/ioport.h>
+#include <linux/types.h>
+
+extern struct resource sk_res;
+
+#endif /* _HV_VSM_H */
diff --git a/drivers/hv/hv_vsm_boot.c b/drivers/hv/hv_vsm_boot.c
index 99e4dc8695837..dc20f935da5b2 100644
--- a/drivers/hv/hv_vsm_boot.c
+++ b/drivers/hv/hv_vsm_boot.c
@@ -6,6 +6,7 @@
  * Copyright (c) 2023-2025, Microsoft Corporation.
  *
  * Author: Thara Gopinath <[email protected]>
+ *         Stanislav Kinsburskii <[email protected]>
  *
  */
 
@@ -13,10 +14,26 @@
 
 #include <linux/hyperv.h>
 #include <linux/cpumask.h>
+#include <linux/namei.h>
+#include <linux/acpi.h>
+#include <linux/firmware.h>
+#include <hyperv/vsm.h>
+#include <asm/e820/types.h>
 #include <asm/mshyperv.h>
 #include "mshv.h"
+#include "hv_vsm.h"
 
 #define HV_VTL1_ENABLE_BIT     BIT(1)
+/*
+ * Firmware name looked up via request_firmware() under /lib/firmware/.
+ *
+ * The secure kernel image is expected to be delivered inside the signed
+ * UKI/initramfs so that it is authenticated end-to-end via Secure Boot
+ * before request_firmware() returns it.
+ */
+#define SK_FW_NAME             "vsm_sk"
+
+static void *vsm_skm_va;
 
 static int hv_vsm_get_register(u32 reg_name, u64 *result)
 {
@@ -38,6 +55,167 @@ static int hv_vsm_get_register(u32 reg_name, u64 *result)
        return 0;
 }
 
+static Elf64_Addr __init hv_vsm_elf_min_load_paddr(void *image)
+{
+       Elf64_Ehdr *ehdr = image;
+       Elf64_Phdr *phdr = image + ehdr->e_phoff;
+       Elf64_Addr paddr = U64_MAX;
+       int i;
+
+       for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+               if (phdr->p_type != PT_LOAD)
+                       continue;
+
+               if (phdr->p_paddr < paddr)
+                       paddr = phdr->p_paddr;
+       }
+
+       return paddr;
+}
+
+static size_t __init hv_vsm_elf_binary_size(void *image)
+{
+       Elf64_Ehdr *ehdr = image;
+       Elf64_Phdr *phdr = image + ehdr->e_phoff;
+       Elf64_Addr min_paddr, max_paddr = 0;
+       int i;
+
+       min_paddr = hv_vsm_elf_min_load_paddr(image);
+       if (min_paddr == U64_MAX)
+               return 0;
+
+       for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+               if (phdr->p_type != PT_LOAD)
+                       continue;
+
+               max_paddr = max(max_paddr, phdr->p_paddr + phdr->p_memsz);
+       }
+
+       return max_paddr - min_paddr;
+}
+
+static int __init hv_vsm_load_elf(void *image, Elf64_Addr *sk_entry_pa)
+{
+       Elf64_Ehdr *ehdr = image;
+       Elf64_Phdr *phdr = image + ehdr->e_phoff;
+       Elf64_Addr min_paddr;
+       Elf64_Xword first_load_align = 0;
+       size_t size;
+       void *base_addr;
+       int i;
+
+       /* Find alignment of the first PT_LOAD segment. */
+       for (i = 0; i < ehdr->e_phnum; i++) {
+               if (phdr[i].p_type == PT_LOAD) {
+                       first_load_align = phdr[i].p_align;
+                       break;
+               }
+       }
+       if (!first_load_align) {
+               pr_err("Secure kernel does not have loadable segments\n");
+               return -EINVAL;
+       }
+
+       /* Align the base load address up to the first PT_LOAD segment 
alignment */
+       base_addr = PTR_ALIGN(vsm_skm_va + first_load_align, first_load_align);
+
+       size = hv_vsm_elf_binary_size(image);
+       if (vsm_skm_va + VSM_SK_INITIAL_MAP_SIZE - base_addr < size) {
+               pr_err("secure kernel does not fit: %zu > %td\n", size,
+                      vsm_skm_va + VSM_SK_INITIAL_MAP_SIZE - base_addr);
+               return -EFBIG;
+       }
+
+       pr_debug("secure kernel binary size: %#zx\n", size);
+
+       min_paddr = hv_vsm_elf_min_load_paddr(image);
+       pr_debug("secure kernel minimal paddr: %#llx\n", min_paddr);
+
+       pr_debug("loading secure kernel ELF segments:\n");
+
+       /* Validate PT_LOAD alignment first, before touching any target memory. 
*/
+       for (i = 0; i < ehdr->e_phnum; i++) {
+               if (phdr[i].p_type != PT_LOAD)
+                       continue;
+               if (phdr[i].p_align % SZ_2M) {
+                       pr_err("LOAD segment is not aligned by 2MB\n");
+                       return -EINVAL;
+               }
+       }
+
+       for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+               void *load_addr;
+
+               if (phdr->p_type != PT_LOAD)
+                       continue;
+
+               /*
+                * Adjust the load address by min_paddr to compensate the
+                * offset.
+                */
+               load_addr = base_addr + (phdr->p_paddr - min_paddr);
+
+               pr_debug("  p_offset: %#016llx, p_filesz: %#016llx, p_memsz: 
%#016llx to pa %#016llx\n",
+                        phdr->p_offset, phdr->p_filesz, phdr->p_memsz,
+                        virt_to_phys(load_addr));
+               memcpy(load_addr, image + phdr->p_offset, phdr->p_filesz);
+
+               if (phdr->p_memsz == phdr->p_filesz)
+                       continue;
+
+               pr_debug("    zeroing %#016llx bytes at pa %#016llx\n",
+                        phdr->p_memsz - phdr->p_filesz,
+                        virt_to_phys(load_addr + phdr->p_filesz));
+               memset(load_addr + phdr->p_filesz, 0,
+                      phdr->p_memsz - phdr->p_filesz);
+       }
+
+       *sk_entry_pa = virt_to_phys(base_addr + (ehdr->e_entry - min_paddr));
+       pr_debug("secure kernel entry pa: %#llx\n", *sk_entry_pa);
+
+       return 0;
+}
+
+static int __init hv_vsm_load_secure_kernel(Elf64_Addr *sk_entry_pa)
+{
+       const struct firmware *fw;
+       Elf64_Ehdr *ehdr;
+       int ret;
+
+       ret = request_firmware(&fw, SK_FW_NAME, NULL);
+       if (ret) {
+               pr_err("Failed to load %s firmware: %d\n", SK_FW_NAME, ret);
+               return ret;
+       }
+
+       ehdr = (Elf64_Ehdr *)fw->data;
+       if (fw->size < sizeof(*ehdr) ||
+           memcmp(ehdr->e_ident, ELFMAG, SELFMAG) ||
+           (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)) {
+               pr_err("Not a valid ELF file: %s\n", SK_FW_NAME);
+               ret = -ENOEXEC;
+               goto out_release;
+       }
+
+       if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) {
+               pr_err("Not a 64-bit compatible ELF file: %s\n", SK_FW_NAME);
+               ret = -ENOEXEC;
+               goto out_release;
+       }
+
+       if (!elf_check_arch(ehdr)) {
+               pr_err("Not a valid ELF file: %s\n", SK_FW_NAME);
+               ret = -ENOEXEC;
+               goto out_release;
+       }
+
+       ret = hv_vsm_load_elf((void *)fw->data, sk_entry_pa);
+
+out_release:
+       release_firmware(fw);
+       return ret;
+}
+
 static int __init hv_vsm_enable_partition_vtl(void)
 {
        u64 status = 0;
@@ -85,6 +263,7 @@ static int __init hv_vsm_bootstrap_vtl(void)
 {
        u16 partition_enabled_vtl_set = 0, partition_mbec_enabled_vtl_set = 0;
        u8 partition_max_vtl;
+       Elf64_Addr sk_entry_pa;
        int ret;
 
        /* Check and enable VTL1 at the partition level */
@@ -120,7 +299,25 @@ static int __init hv_vsm_bootstrap_vtl(void)
                        return -EINVAL;
                }
        }
-       return 0;
+
+       return hv_vsm_load_secure_kernel(&sk_entry_pa);
+}
+
+static void __init hv_vsm_get_sk_mem(void)
+{
+       /*
+        * The reserved secure kernel region is mandatory once VSM support has
+        * been advertised. Without it we cannot load the secure kernel and
+        * bringing up VTL1 is impossible, so fail hard rather than continuing
+        * in an unusable state.
+        */
+       if (!sk_res.start)
+               panic("No memory reserved in cmdline for secure kernel");
+
+       vsm_skm_va = phys_to_virt(sk_res.start);
+
+       pr_info("secure kernel region: %#llx-%#llx (%lld MB)\n",
+               sk_res.start, sk_res.end, resource_size(&sk_res) >> 20);
 }
 
 static bool __init vsm_arch_has_vsm_access(void)
@@ -143,6 +340,8 @@ static int __init hv_vsm_boot_init(void)
        if (!vsm_arch_has_vsm_access())
                return 0;
 
+       hv_vsm_get_sk_mem();
+
        /*
         * 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.
diff --git a/include/hyperv/vsm.h b/include/hyperv/vsm.h
new file mode 100644
index 0000000000000..51555c09413d5
--- /dev/null
+++ b/include/hyperv/vsm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Common definitions shared by generic and arch code for enabling VTL1
+ * and the Virtual Secure Mode (VSM) framework on Microsoft Hyper-V.
+ *
+ * Copyright (c) 2025-2026, Microsoft Corporation.
+ *
+ * Author: Thara Gopinath <[email protected]>
+ */
+
+#ifndef _HYPERV_VSM_H
+#define _HYPERV_VSM_H
+
+/*
+ * Size of memory that is initially mapped for the secure kernel by the
+ * VTL0-side loader. The secure kernel image itself may be larger than
+ * this and map additional memory on its own.
+ */
+#define VSM_SK_INITIAL_MAP_SIZE                (16 * 1024 * 1024)
+
+#endif /* _HYPERV_VSM_H */
-- 
2.34.1


Reply via email to