* [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes
@ 2026-08-05 11:02 Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 01/42] Fix merge issue - Remove duplicate definition for kvm_arch_has_irq_bypass Sriram Nambakam
` (41 more replies)
0 siblings, 42 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
This RFC demonstrates how KVM VM Planes can be used to implement
Virtualization-Based Security (VBS) using Virtual Trust Levels (VTLs).
It provides a concrete implementation for discussion and is intended to
solicit feedback on the architecture, kernel interfaces, and division of
responsibility between KVM, the normal guest kernel, and a secure kernel.
The complete implementation is available in three repositories under the
safe-tee organization:
Linux and KVM support:
https://github.com/safe-tee/linux/tree/kvm-planes-v7.2-rc5
QEMU support:
https://github.com/safe-tee/qemu/tree/qemu-planes-v11.1.0-rc1
Build, test, and integration tooling:
https://github.com/safe-tee/lvbs
Current build and run instructions are maintained in the lvbs repository:
Build the Plane 0 kernel, Plane 1 secure kernel, VM image, and run QEMU:
https://github.com/safe-tee/lvbs/blob/main/docs/how-to/how-to-build.md
The VBS core is implemented under security/vbs and exposes a
transport-independent interface through include/linux/vbs.h. The normal
guest kernel runs in Plane 0 (VTL0) and requests security services from a
higher-trust kernel running in Plane 1 (VTL1). For KVM VM Planes, these
requests are transported through a shared calling page and a KVM
hypercall-mediated plane switch.
The RFC demonstrates loading and starting the secure kernel, communication
between VTL0 and VTL1, and hypervisor-enforced kernel integrity (HEKI).
The secure plane can protect VTL0 memory independently of VTL0, seal kernel
text and read-only data, participate in module validation and permission
changes, and validate kexec images.
This mail series contains only the 42 VBS and integration patches authored
by me. It does not duplicate the prerequisite VM Planes patches from Joerg
Roedel and Paolo Bonzini. The Linux branch linked above contains the full
integrated tree, including their patches and related KVM work from Melody
Wang and Tom Lendacky, used for development and testing.
This is prototype code and is not yet intended for production use. In
particular, the KVM backend currently routes VTL calls through CPU 0 and
the VM Planes configuration requires a split in-kernel IRQ chip.
Feedback on the overall model, the VBS interface, and how this work should
be split for upstream review would be appreciated.
Acknowledgments
===============
This work stands on top of, and is indebted to, several prior efforts:
- Joerg Roedel, whose KVM VM Planes tree provides the in-kernel plane
infrastructure that the VBS/VSM secure plane relies on.
- Paolo Bonzini, whose "[RFC PATCH 00/29] KVM: VM planes" introduced the
VM plane concept to KVM as a common in-kernel model for AMD VMPLs,
Intel TDX partitions, Hyper-V VTLs, and Arm CCA planes. Joerg Roedel's
work carries that model forward.
https://lwn.net/Articles/1016113/
- The prior Linux VBS (LVBS) and Hypervisor-Enforced Kernel Integrity
(HEKI) work by James Morris, Thara Gopinath, Wei Liu,
Madhavan Venkataraman, and Mickaël Salaün, posted as
"[RFC PATCH v2 00/19] Hypervisor-Enforced Kernel Integrity (HEKI)".
The kernel-sealing model used here follows their design.
- James Bottomley and James Morris, for their ongoing work on VSM-on-KVM,
which informed the design and direction of this series.
Sriram Nambakam (42):
Fix merge issue - Remove duplicate definition for
kvm_arch_has_irq_bypass
Fix compilation
Fix compile error
Fix compile errors
Initial support for VM Planes - Add kernel config for CONFIG_VM_PLANES
- Parse vm plane config from initrd for plane configuration - Make
hypercalls to allocate memory for the vm planes.
Use vcpu count from the plane configuration
skip processing plane configuration for plane 0 - plane 0 is the boot
plane
Add plane config param to specify kernel image format
Activate the VM Planes through the Hypervisor - Using KVM as the VMM
allow the command line to be specified for kernels in other planes
Various changes to support VM Planes.
Add a Virtualization Based Security (VBS) framework. - Add backends
for AMD SEV-SNP, Intel TDX, Arm CCA and KVM Planes. - Support VTL on
Hyper-V in addition to Planes on KVM.
Add a inter-plane communication mechanism through KVM. - model this to
use a single page similar to SEV-SNP
KVM: Add per-plane memory attribute support for cross-plane EPT
protection
KVM: x86: Add KVM_HC_VBS_VTL_CALL hypercall for VBS inter-plane calls
vbs: Add HEKI kernel sealing and fix KVM plane memory attribute guards
vbs: Add module authentication via VBS/HEKI
vbs: Add kexec validation and make module auth non-fatal
Merge branch 'master' into vm-planes
kvm: x86: fix merged plane API/stat build regressions
KVM: x86: exit VM planes and VBS hypercalls to userspace
kexec: block legacy kexec_load when VBS is active
kvm: x86: fix merged plane API/stat build regressions
KVM: planes: expose memory-attribute setting to in-kernel callers
vm_planes: drop unused per-plane vcpu_count
drivers/virt: add VBS secure-plane park loop
KVM: planes: add arch-neutral in-kernel plane switch helper
KVM: x86: add VBS VTL call/return and cross-plane set-mem-attrs
hypercalls
init/vm_planes: set up planes from rootfs_initcall and load ELF
payloads
security/vbs: run backend probe and HEKI seal at rootfs_initcall
security/vbs: pin the VTL call hypercall to CPU0
security/vbs: add secure-plane monitor backend
drivers/virt: rename VBS park loop to secure_monitor
x86/realmode: skip the sub-1M trampoline for the VBS secure plane
KVM: x86: deny normal-plane access to secure-plane memory
KVM: plane: handle KVM_CHECK_EXTENSION on the plane fd
KVM: selftests: run plane tests with a split IRQ chip
kvm: x86: drop obsolete kvm_cache_regs.h
kvm: arch: finalize plane hooks and kvm_arch_vcpu_create signature
kvm: x86: use kvm_vcpu scheduling-state accessors and struct stat
fields
kvm: x86: finalize per-plane APIC state and CPUID placement
kvm: planes: reconcile core plane state, UAPI and hypercall exit
--
2.55.0
^ permalink raw reply [flat|nested] 43+ messages in thread
* [RFC PATCH v1 01/42] Fix merge issue - Remove duplicate definition for kvm_arch_has_irq_bypass
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 02/42] Fix compilation Sriram Nambakam
` (40 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
From: Sriram Nambakam <snambakam@microsoft.com>
---
arch/x86/include/asm/kvm_host.h | 5 -----
1 file changed, 5 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 979d1749f176..b7d478dcc1a5 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2603,11 +2603,6 @@ static inline bool kvm_arch_has_irq_bypass(void)
return enable_device_posted_irqs;
}
-static inline bool kvm_arch_has_irq_bypass(void)
-{
- return enable_device_posted_irqs;
-}
-
int kvm_arch_nr_vcpu_planes(struct kvm *kvm);
bool kvm_arch_planes_share_fpu(struct kvm *kvm);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 02/42] Fix compilation
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 01/42] Fix merge issue - Remove duplicate definition for kvm_arch_has_irq_bypass Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 03/42] Fix compile error Sriram Nambakam
` (39 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
From: Sriram Nambakam <snambakam@microsoft.com>
---
arch/x86/kvm/irq_comm.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/irq_comm.c b/arch/x86/kvm/irq_comm.c
index 94f9db50384e..55f395f9d8af 100644
--- a/arch/x86/kvm/irq_comm.c
+++ b/arch/x86/kvm/irq_comm.c
@@ -122,7 +122,7 @@ void kvm_set_msi_irq(struct kvm *kvm, struct kvm_kernel_irq_routing_entry *e,
irq->shorthand = APIC_DEST_NOSHORT;
irq->plane = e->msi.plane;
}
-EXPORT_SYMBOL_GPL(kvm_set_msi_irq);
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_set_msi_irq);
static inline bool kvm_msi_route_invalid(struct kvm *kvm,
struct kvm_kernel_irq_routing_entry *e)
@@ -369,7 +369,7 @@ bool kvm_intr_is_single_vcpu(struct kvm *kvm, struct kvm_lapic_irq *irq,
return r == 1;
}
-EXPORT_SYMBOL_GPL(kvm_intr_is_single_vcpu);
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_intr_is_single_vcpu);
#define IOAPIC_ROUTING_ENTRY(irq) \
{ .gsi = irq, .type = KVM_IRQ_ROUTING_IRQCHIP, \
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 03/42] Fix compile error
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 01/42] Fix merge issue - Remove duplicate definition for kvm_arch_has_irq_bypass Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 02/42] Fix compilation Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 04/42] Fix compile errors Sriram Nambakam
` (38 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
From: Sriram Nambakam <snambakam@microsoft.com>
---
arch/x86/kvm/vmx/vmx.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 03e1ce935799..ee1d606e3314 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -417,7 +417,7 @@ static noinstr void vmx_l1d_flush(struct kvm_vcpu *vcpu)
kvm_clear_cpu_l1tf_flush_l1d();
}
- vcpu->stat.l1d_flush++;
+ vcpu->stat->l1d_flush++;
if (static_cpu_has(X86_FEATURE_FLUSH_L1D)) {
native_wrmsrq(MSR_IA32_FLUSH_CMD, L1D_FLUSH);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 04/42] Fix compile errors
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (2 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 03/42] Fix compile error Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 05/42] Initial support for VM Planes - Add kernel config for CONFIG_VM_PLANES - Parse vm plane config from initrd for plane configuration - Make hypercalls to allocate memory for the vm planes Sriram Nambakam
` (37 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
From: Sriram Nambakam <snambakam@microsoft.com>
---
arch/x86/kvm/svm/avic.c | 2 +-
arch/x86/kvm/svm/sev.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 58e493a80cb0..251e36f5f0f7 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -404,7 +404,7 @@ static int avic_init_backing_page(struct kvm_vcpu *vcpu)
* fully initialized AVIC.
*/
if (id > max_id) {
- kvm_set_apicv_inhibit(vcpu->kvm, APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG);
+ kvm_set_apicv_inhibit(vcpu->kvm->planes[0], APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG);
vcpu->arch.apic->apicv_active = false;
return 0;
}
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index fe5e05e2162d..79fee7ebc19b 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -569,7 +569,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
INIT_LIST_HEAD(&sev->mirror_vms);
sev->need_init = false;
- kvm_set_apicv_inhibit(kvm->planes[[0], APICV_INHIBIT_REASON_SEV);
+ kvm_set_apicv_inhibit(kvm->planes[0], APICV_INHIBIT_REASON_SEV);
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 05/42] Initial support for VM Planes - Add kernel config for CONFIG_VM_PLANES - Parse vm plane config from initrd for plane configuration - Make hypercalls to allocate memory for the vm planes.
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (3 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 04/42] Fix compile errors Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 06/42] Use vcpu count from the plane configuration Sriram Nambakam
` (36 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
arch/x86/include/asm/cpu.h | 8 +
arch/x86/kernel/cpu/common.c | 38 ++++
include/linux/vm_planes.h | 22 ++
init/Kconfig | 16 ++
init/Makefile | 1 +
init/main.c | 4 +
init/vm_planes.c | 417 +++++++++++++++++++++++++++++++++++
7 files changed, 506 insertions(+)
create mode 100644 include/linux/vm_planes.h
create mode 100644 init/vm_planes.c
diff --git a/arch/x86/include/asm/cpu.h b/arch/x86/include/asm/cpu.h
index 57a0786dfd75..8ab76adf14a9 100644
--- a/arch/x86/include/asm/cpu.h
+++ b/arch/x86/include/asm/cpu.h
@@ -4,11 +4,19 @@
#include <linux/device.h>
#include <linux/cpu.h>
+#include <linux/init.h>
#include <linux/topology.h>
#include <linux/nodemask.h>
#include <linux/percpu.h>
#include <asm/ibt.h>
+#ifdef CONFIG_VM_PLANES
+struct vm_plane_config;
+
+void __init alloc_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
+#endif
+
#ifndef CONFIG_SMP
#define cpu_physical_id(cpu) boot_cpu_physical_apicid
#endif /* CONFIG_SMP */
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index a3df21d26460..166597204739 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -20,11 +20,13 @@
#include <linux/kprobes.h>
#include <linux/kgdb.h>
#include <linux/mem_encrypt.h>
+#include <linux/kvm_para.h>
#include <linux/smp.h>
#include <linux/cpu.h>
#include <linux/io.h>
#include <linux/syscore_ops.h>
#include <linux/pgtable.h>
+#include <linux/vm_planes.h>
#include <linux/stackprotector.h>
#include <linux/utsname.h>
#include <linux/efi.h>
@@ -32,6 +34,7 @@
#include <asm/alternative.h>
#include <asm/cmdline.h>
#include <asm/cpuid/api.h>
+#include <asm/kvm_para.h>
#include <asm/perf_event.h>
#include <asm/mmu_context.h>
#include <asm/doublefault.h>
@@ -77,6 +80,11 @@
#include "cpu.h"
+#ifdef CONFIG_VM_PLANES
+/* Private hypercall number for early VM plane configuration. */
+#define KVM_HC_VM_PLANES_CONFIG 0x1000
+#endif
+
DEFINE_PER_CPU_READ_MOSTLY(struct cpuinfo_x86, cpu_info);
EXPORT_PER_CPU_SYMBOL(cpu_info);
@@ -2664,3 +2672,33 @@ void __init arch_cpu_finalize_init(void)
*/
mem_encrypt_init();
}
+
+#ifdef CONFIG_VM_PLANES
+void __init alloc_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg)
+{
+ phys_addr_t phys;
+ long ret;
+
+ if (!plane_count || !plane_cfg)
+ return;
+
+ if (!kvm_para_available()) {
+ pr_warn("vm_planes: hypercall interface unavailable\n");
+ return;
+ }
+
+ phys = virt_to_phys((void *)plane_cfg);
+
+ if (sizeof(unsigned long) < sizeof(phys_addr_t) && phys > ULONG_MAX) {
+ pr_warn("vm_planes: shared config address exceeds hypercall register width\n");
+ return;
+ }
+
+ ret = kvm_hypercall2(KVM_HC_VM_PLANES_CONFIG,
+ (unsigned long)phys,
+ plane_count);
+ if (ret < 0)
+ pr_warn("vm_planes: hypercall failed: %ld\n", ret);
+}
+#endif
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
new file mode 100644
index 000000000000..4a91dc79b7ba
--- /dev/null
+++ b/include/linux/vm_planes.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_VM_PLANES_H
+#define _LINUX_VM_PLANES_H
+
+#include <linux/init.h>
+#include <linux/types.h>
+
+#ifdef CONFIG_VM_PLANES
+
+#define VM_PLANE_KERNEL_NAME_MAX 128
+
+struct vm_plane_config {
+ phys_addr_t load_offset;
+ phys_addr_t memory_size;
+ char kernel[VM_PLANE_KERNEL_NAME_MAX];
+};
+
+void __init arch_init_vm_planes(void);
+
+#endif /* CONFIG_VM_PLANES */
+
+#endif /* _LINUX_VM_PLANES_H */
diff --git a/init/Kconfig b/init/Kconfig
index 10f2013b5321..23d9cca334ba 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1713,6 +1713,22 @@ menuconfig EXPERT
environments which can tolerate a "non-standard" kernel.
Only use this if you really know what you are doing.
+config VM_PLANES
+ bool "Enable VM planes early boot support" if EXPERT
+ default n
+ help
+ Enable hypervisor enabled multi-kernel support.
+
+ This allows processing the kernel command-line parameter
+ "enable-vm-planes" and, when requested, calling
+ arch_init_vm_planes() during start_kernel().
+
+ The initrd config-vm-planes file is expected to provide per-plane
+ entries for PLANE_<id>_KERNEL, PLANE_<id>_LOAD_OFFSET, and
+ PLANE_<id>_MEMORY_SIZE.
+
+ If unsure, say N.
+
config UID16
bool "Enable 16-bit UID system calls" if EXPERT
depends on HAVE_UID16 && MULTIUSER
diff --git a/init/Makefile b/init/Makefile
index d6f75d8907e0..113133c8cdd7 100644
--- a/init/Makefile
+++ b/init/Makefile
@@ -6,6 +6,7 @@
ccflags-y := -fno-function-sections -fno-data-sections
obj-y := main.o version.o mounts.o
+obj-y += vm_planes.o
ifneq ($(CONFIG_BLK_DEV_INITRD),y)
obj-y += noinitramfs.o
else
diff --git a/init/main.c b/init/main.c
index e363232b428b..3e35c2caca17 100644
--- a/init/main.c
+++ b/init/main.c
@@ -40,6 +40,7 @@
#include <linux/vmalloc.h>
#include <linux/kernel_stat.h>
#include <linux/start_kernel.h>
+#include <linux/vm_planes.h>
#include <linux/security.h>
#include <linux/smp.h>
#include <linux/profile.h>
@@ -993,6 +994,9 @@ void start_kernel(void)
pr_notice("%s", linux_banner);
setup_arch(&command_line);
mm_core_init_early();
+#ifdef CONFIG_VM_PLANES
+ arch_init_vm_planes();
+#endif
/* Static keys and static calls are needed by LSMs */
jump_label_init();
static_call_init();
diff --git a/init/vm_planes.c b/init/vm_planes.c
new file mode 100644
index 000000000000..30f17da92332
--- /dev/null
+++ b/init/vm_planes.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/init.h>
+#include <linux/initrd.h>
+#include <linux/kernel.h>
+#include <linux/memblock.h>
+#include <linux/kstrtox.h>
+#include <linux/string.h>
+#include <linux/kvm_para.h>
+#include <linux/vm_planes.h>
+#include <asm/cpu.h>
+#include <asm/kvm_para.h>
+
+#ifdef CONFIG_VM_PLANES
+static bool __initdata enable_vm_planes_requested;
+
+#define VM_PLANES_CONFIG_FILE "config-vm-planes"
+#define VM_PLANES_DEFAULT_COUNT 1
+
+struct vm_plane_parse_state {
+ phys_addr_t have_load_offset;
+ phys_addr_t have_memory_size;
+ char have_kernel[VM_PLANE_KERNEL_NAME_MAX];
+};
+
+#define VM_PLANES_UNSET_VALUE ((phys_addr_t)~0)
+
+struct cpio_newc_header {
+ char c_magic[6];
+ char c_ino[8];
+ char c_mode[8];
+ char c_uid[8];
+ char c_gid[8];
+ char c_nlink[8];
+ char c_mtime[8];
+ char c_filesize[8];
+ char c_devmajor[8];
+ char c_devminor[8];
+ char c_rdevmajor[8];
+ char c_rdevminor[8];
+ char c_namesize[8];
+ char c_check[8];
+};
+
+static int __init parse_hex_field(const char *field, size_t len, u32 *value)
+{
+ u32 v = 0;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ u8 c = field[i];
+
+ v <<= 4;
+ if (c >= '0' && c <= '9')
+ v |= c - '0';
+ else if (c >= 'a' && c <= 'f')
+ v |= c - 'a' + 10;
+ else if (c >= 'A' && c <= 'F')
+ v |= c - 'A' + 10;
+ else
+ return -EINVAL;
+ }
+
+ *value = v;
+ return 0;
+}
+
+static int __init parse_plane_count_line(const char *line, size_t len,
+ unsigned int *plane_count)
+{
+ const char *keys[] = { "PLANE_COUNT=", "CONFIG_PLANE_COUNT=" };
+ unsigned int i;
+
+ while (len && (*line == ' ' || *line == '\t')) {
+ line++;
+ len--;
+ }
+
+ if (!len || *line == '#')
+ return -ENOENT;
+
+ for (i = 0; i < ARRAY_SIZE(keys); i++) {
+ size_t key_len = strlen(keys[i]);
+ size_t val_len = 0;
+ char tmp[32];
+
+ if (len <= key_len || strncmp(line, keys[i], key_len))
+ continue;
+
+ line += key_len;
+ len -= key_len;
+ while (val_len < len && line[val_len] != ' ' &&
+ line[val_len] != '\t' && line[val_len] != '#')
+ val_len++;
+
+ if (!val_len || val_len >= sizeof(tmp))
+ return -EINVAL;
+
+ memcpy(tmp, line, val_len);
+ tmp[val_len] = '\0';
+
+ if (kstrtouint(tmp, 0, plane_count))
+ return -EINVAL;
+ if (!*plane_count)
+ return -EINVAL;
+
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int __init parse_plane_count_kconfig(const char *buf, size_t len,
+ unsigned int *plane_count)
+{
+ const char *p = buf;
+ const char *end = buf + len;
+
+ while (p < end) {
+ const char *eol = memchr(p, '\n', end - p);
+ size_t line_len = eol ? (size_t)(eol - p) : (size_t)(end - p);
+ int ret = parse_plane_count_line(p, line_len, plane_count);
+
+ if (!ret)
+ return 0;
+
+ p += line_len;
+ if (p < end && *p == '\n')
+ p++;
+ }
+
+ return -ENOENT;
+}
+
+static int __init parse_plane_cfg_line(const char *line, size_t len,
+ unsigned int plane_count,
+ struct vm_plane_config *plane_cfg,
+ struct vm_plane_parse_state *state)
+{
+ char tmp[192];
+ char *p, *key, *val;
+ unsigned int plane_id;
+ u64 parsed_u64;
+ phys_addr_t parsed;
+
+ if (len >= sizeof(tmp))
+ return -E2BIG;
+
+ memcpy(tmp, line, len);
+ tmp[len] = '\0';
+
+ p = strim(tmp);
+ if (!*p || *p == '#')
+ return -ENOENT;
+
+ val = strchr(p, '#');
+ if (val)
+ *val = '\0';
+ p = strim(p);
+ if (!*p)
+ return -ENOENT;
+
+ if (!strncmp(p, "CONFIG_", 7))
+ p += 7;
+
+ if (strncmp(p, "PLANE_", 6))
+ return -ENOENT;
+ p += 6;
+
+ key = strchr(p, '_');
+ if (!key)
+ return -EINVAL;
+ *key++ = '\0';
+
+ if (kstrtouint(p, 10, &plane_id) || plane_id >= plane_count)
+ return -EINVAL;
+
+ val = strchr(key, '=');
+ if (!val)
+ return -EINVAL;
+ *val++ = '\0';
+
+ key = strim(key);
+ val = strim(val);
+ if (!*val)
+ return -EINVAL;
+
+ if (!strcmp(key, "KERNEL")) {
+ size_t val_len = strlen(val);
+
+ if (val[0] == '"') {
+ if (val_len < 2 || val[val_len - 1] != '"')
+ return -EINVAL;
+ val[val_len - 1] = '\0';
+ val++;
+ val = strim(val);
+ }
+
+ if (!*val)
+ return -EINVAL;
+
+ if (strscpy(plane_cfg[plane_id].kernel, val,
+ sizeof(plane_cfg[plane_id].kernel)) < 0)
+ return -EINVAL;
+
+ strscpy(state[plane_id].have_kernel, val,
+ sizeof(state[plane_id].have_kernel));
+ return 0;
+ }
+
+ if (kstrtou64(val, 0, &parsed_u64))
+ return -EINVAL;
+
+ if (parsed_u64 > (u64)VM_PLANES_UNSET_VALUE)
+ return -ERANGE;
+
+ parsed = (phys_addr_t)parsed_u64;
+
+ if (!strcmp(key, "LOAD_OFFSET")) {
+ plane_cfg[plane_id].load_offset = parsed;
+ state[plane_id].have_load_offset = parsed;
+ return 0;
+ }
+
+ if (!strcmp(key, "MEMORY_SIZE")) {
+ plane_cfg[plane_id].memory_size = parsed;
+ state[plane_id].have_memory_size = parsed;
+ return 0;
+ }
+
+ return -ENOENT;
+}
+
+static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
+ unsigned int *plane_count,
+ struct vm_plane_config **plane_cfg)
+{
+ const char *p = buf;
+ const char *end = buf + len;
+ struct vm_plane_parse_state *state;
+ unsigned int i;
+ int ret;
+
+ ret = parse_plane_count_kconfig(buf, len, plane_count);
+ if (ret)
+ return ret;
+
+ if (*plane_count > UINT_MAX / sizeof(**plane_cfg))
+ return -E2BIG;
+
+ *plane_cfg = memblock_alloc(*plane_count * sizeof(**plane_cfg),
+ SMP_CACHE_BYTES);
+ if (!*plane_cfg)
+ return -ENOMEM;
+
+ state = memblock_alloc(*plane_count * sizeof(*state), SMP_CACHE_BYTES);
+ if (!state)
+ return -ENOMEM;
+
+ memset(*plane_cfg, 0, *plane_count * sizeof(**plane_cfg));
+ for (i = 0; i < *plane_count; i++) {
+ state[i].have_load_offset = VM_PLANES_UNSET_VALUE;
+ state[i].have_memory_size = VM_PLANES_UNSET_VALUE;
+ state[i].have_kernel[0] = '\0';
+ }
+
+ while (p < end) {
+ const char *eol = memchr(p, '\n', end - p);
+ size_t line_len = eol ? (size_t)(eol - p) : (size_t)(end - p);
+
+ ret = parse_plane_cfg_line(p, line_len, *plane_count,
+ *plane_cfg, state);
+ if (ret && ret != -ENOENT)
+ return ret;
+
+ p += line_len;
+ if (p < end && *p == '\n')
+ p++;
+ }
+
+ for (i = 0; i < *plane_count; i++) {
+ if (state[i].have_load_offset == VM_PLANES_UNSET_VALUE ||
+ state[i].have_memory_size == VM_PLANES_UNSET_VALUE ||
+ !state[i].have_kernel[0])
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static bool __init cpio_name_match(const char *name, size_t namesize,
+ const char *target)
+{
+ while (namesize > 1 && (*name == '/' ||
+ (namesize > 2 && name[0] == '.' && name[1] == '/'))) {
+ if (*name == '/') {
+ name++;
+ namesize--;
+ } else {
+ name += 2;
+ namesize -= 2;
+ }
+ }
+
+ return !strncmp(name, target, namesize - 1) &&
+ strlen(target) == namesize - 1;
+}
+
+static int __init vm_planes_get_cfg_from_initrd(unsigned int *plane_count,
+ struct vm_plane_config **plane_cfg)
+{
+ const u8 *p = (const u8 *)(unsigned long)initrd_start;
+ const u8 *end = (const u8 *)(unsigned long)initrd_end;
+
+ if (!initrd_start || !initrd_end || initrd_end <= initrd_start)
+ return -ENOENT;
+
+ while (p + sizeof(struct cpio_newc_header) <= end) {
+ const struct cpio_newc_header *hdr;
+ const char *name;
+ const u8 *data;
+ u32 namesize, filesize;
+ u32 name_align, data_align;
+ int ret;
+
+ hdr = (const struct cpio_newc_header *)p;
+ if (memcmp(hdr->c_magic, "070701", 6) &&
+ memcmp(hdr->c_magic, "070702", 6))
+ return -EINVAL;
+
+ ret = parse_hex_field(hdr->c_namesize, sizeof(hdr->c_namesize), &namesize);
+ if (ret)
+ return ret;
+
+ ret = parse_hex_field(hdr->c_filesize, sizeof(hdr->c_filesize), &filesize);
+ if (ret)
+ return ret;
+
+ if (!namesize)
+ return -EINVAL;
+
+ p += sizeof(*hdr);
+ if (p + namesize > end)
+ return -EINVAL;
+
+ name = (const char *)p;
+ name_align = ALIGN(namesize, 4);
+ if (p + name_align > end)
+ return -EINVAL;
+
+ data = p + name_align;
+ if (data + filesize > end)
+ return -EINVAL;
+
+ if (!strcmp(name, "TRAILER!!!"))
+ break;
+
+ if (cpio_name_match(name, namesize, VM_PLANES_CONFIG_FILE))
+ return parse_vm_planes_kconfig((const char *)data,
+ filesize,
+ plane_count,
+ plane_cfg);
+
+ data_align = ALIGN(filesize, 4);
+ if (data + data_align < data || data + data_align > end)
+ return -EINVAL;
+
+ p = data + data_align;
+ }
+
+ return -ENOENT;
+}
+
+static int __init parse_enable_vm_planes(char *str)
+{
+ bool enable;
+
+ if (!str) {
+ enable_vm_planes_requested = true;
+ return 0;
+ }
+
+ if (kstrtobool(str, &enable))
+ return -EINVAL;
+
+ enable_vm_planes_requested = enable;
+ return 0;
+}
+
+early_param("enable-vm-planes", parse_enable_vm_planes);
+
+void __init __weak alloc_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg) { }
+
+void __init arch_init_vm_planes(void)
+{
+ unsigned int plane_count = VM_PLANES_DEFAULT_COUNT;
+ struct vm_plane_config *plane_cfg;
+
+ if (!enable_vm_planes_requested)
+ return;
+
+ if (!kvm_para_available())
+ return;
+
+ if (vm_planes_get_cfg_from_initrd(&plane_count, &plane_cfg)) {
+ pr_warn("vm_planes: failed to parse %s from initrd\n",
+ VM_PLANES_CONFIG_FILE);
+ return;
+ }
+
+ pr_info("vm_planes: enabling %u planes (ids 0..%u)\n",
+ plane_count, plane_count - 1);
+ alloc_vm_planes(plane_count, plane_cfg);
+}
+
+#endif /* CONFIG_VM_PLANES */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 06/42] Use vcpu count from the plane configuration
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (4 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 05/42] Initial support for VM Planes - Add kernel config for CONFIG_VM_PLANES - Parse vm plane config from initrd for plane configuration - Make hypercalls to allocate memory for the vm planes Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 07/42] skip processing plane configuration for plane 0 - plane 0 is the boot plane Sriram Nambakam
` (35 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
include/linux/vm_planes.h | 1 +
init/vm_planes.c | 37 ++++++++++++++++++++++++-------------
2 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index 4a91dc79b7ba..506de566cbd5 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -12,6 +12,7 @@
struct vm_plane_config {
phys_addr_t load_offset;
phys_addr_t memory_size;
+ unsigned int vcpu_count;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
};
diff --git a/init/vm_planes.c b/init/vm_planes.c
index 30f17da92332..8eafb1c5f8cf 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -18,9 +18,10 @@ static bool __initdata enable_vm_planes_requested;
#define VM_PLANES_DEFAULT_COUNT 1
struct vm_plane_parse_state {
- phys_addr_t have_load_offset;
- phys_addr_t have_memory_size;
- char have_kernel[VM_PLANE_KERNEL_NAME_MAX];
+ phys_addr_t load_offset;
+ phys_addr_t memory_size;
+ unsigned int vcpu_count;
+ char kernel[VM_PLANE_KERNEL_NAME_MAX];
};
#define VM_PLANES_UNSET_VALUE ((phys_addr_t)~0)
@@ -203,8 +204,8 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
sizeof(plane_cfg[plane_id].kernel)) < 0)
return -EINVAL;
- strscpy(state[plane_id].have_kernel, val,
- sizeof(state[plane_id].have_kernel));
+ strscpy(state[plane_id].kernel, val,
+ sizeof(state[plane_id].kernel));
return 0;
}
@@ -218,13 +219,21 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
if (!strcmp(key, "LOAD_OFFSET")) {
plane_cfg[plane_id].load_offset = parsed;
- state[plane_id].have_load_offset = parsed;
+ state[plane_id].load_offset = parsed;
return 0;
}
if (!strcmp(key, "MEMORY_SIZE")) {
plane_cfg[plane_id].memory_size = parsed;
- state[plane_id].have_memory_size = parsed;
+ state[plane_id].memory_size = parsed;
+ return 0;
+ }
+
+ if (!strcmp(key, "VCPU_COUNT")) {
+ if (parsed_u64 == 0 || parsed_u64 > UINT_MAX)
+ return -EINVAL;
+ plane_cfg[plane_id].vcpu_count = (unsigned int)parsed_u64;
+ state[plane_id].vcpu_count = (unsigned int)parsed_u64;
return 0;
}
@@ -259,9 +268,10 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
memset(*plane_cfg, 0, *plane_count * sizeof(**plane_cfg));
for (i = 0; i < *plane_count; i++) {
- state[i].have_load_offset = VM_PLANES_UNSET_VALUE;
- state[i].have_memory_size = VM_PLANES_UNSET_VALUE;
- state[i].have_kernel[0] = '\0';
+ state[i].load_offset = VM_PLANES_UNSET_VALUE;
+ state[i].memory_size = VM_PLANES_UNSET_VALUE;
+ state[i].vcpu_count = 0;
+ state[i].kernel[0] = '\0';
}
while (p < end) {
@@ -279,9 +289,10 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
}
for (i = 0; i < *plane_count; i++) {
- if (state[i].have_load_offset == VM_PLANES_UNSET_VALUE ||
- state[i].have_memory_size == VM_PLANES_UNSET_VALUE ||
- !state[i].have_kernel[0])
+ if (state[i].load_offset == VM_PLANES_UNSET_VALUE ||
+ state[i].memory_size == VM_PLANES_UNSET_VALUE ||
+ !state[i].vcpu_count ||
+ !state[i].kernel[0])
return -EINVAL;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 07/42] skip processing plane configuration for plane 0 - plane 0 is the boot plane
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (5 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 06/42] Use vcpu count from the plane configuration Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 08/42] Add plane config param to specify kernel image format Sriram Nambakam
` (34 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
init/vm_planes.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/init/vm_planes.c b/init/vm_planes.c
index 8eafb1c5f8cf..ecfe8de409a8 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -288,7 +288,10 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
p++;
}
- for (i = 0; i < *plane_count; i++) {
+ /* Plane 0 is the already-running boot plane; only secondary planes
+ * must provide full allocation metadata.
+ */
+ for (i = 1; i < *plane_count; i++) {
if (state[i].load_offset == VM_PLANES_UNSET_VALUE ||
state[i].memory_size == VM_PLANES_UNSET_VALUE ||
!state[i].vcpu_count ||
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 08/42] Add plane config param to specify kernel image format
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (6 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 07/42] skip processing plane configuration for plane 0 - plane 0 is the boot plane Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 09/42] Activate the VM Planes through the Hypervisor - Using KVM as the VMM Sriram Nambakam
` (33 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
include/linux/vm_planes.h | 9 +++++++++
init/vm_planes.c | 20 ++++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index 506de566cbd5..6d0066f70349 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -9,14 +9,23 @@
#define VM_PLANE_KERNEL_NAME_MAX 128
+enum vm_plane_kernel_format {
+ VM_PLANE_KFMT_RAW = 0,
+ VM_PLANE_KFMT_BZIMAGE,
+ VM_PLANE_KFMT_ELF,
+};
+
struct vm_plane_config {
phys_addr_t load_offset;
phys_addr_t memory_size;
unsigned int vcpu_count;
+ unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
};
void __init arch_init_vm_planes(void);
+void __init load_vm_plane_kernels(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
#endif /* CONFIG_VM_PLANES */
diff --git a/init/vm_planes.c b/init/vm_planes.c
index ecfe8de409a8..da9c17de4a44 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -8,8 +8,10 @@
#include <linux/string.h>
#include <linux/kvm_para.h>
#include <linux/vm_planes.h>
+#include <linux/elf.h>
#include <asm/cpu.h>
#include <asm/kvm_para.h>
+#include <asm/io.h>
#ifdef CONFIG_VM_PLANES
static bool __initdata enable_vm_planes_requested;
@@ -21,6 +23,7 @@ struct vm_plane_parse_state {
phys_addr_t load_offset;
phys_addr_t memory_size;
unsigned int vcpu_count;
+ unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
};
@@ -209,6 +212,23 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
return 0;
}
+ if (!strcmp(key, "KERNEL_FORMAT")) {
+ unsigned int fmt;
+
+ if (!strcasecmp(val, "raw"))
+ fmt = VM_PLANE_KFMT_RAW;
+ else if (!strcasecmp(val, "bzimage"))
+ fmt = VM_PLANE_KFMT_BZIMAGE;
+ else if (!strcasecmp(val, "elf"))
+ fmt = VM_PLANE_KFMT_ELF;
+ else
+ return -EINVAL;
+
+ plane_cfg[plane_id].kernel_format = fmt;
+ state[plane_id].kernel_format = fmt;
+ return 0;
+ }
+
if (kstrtou64(val, 0, &parsed_u64))
return -EINVAL;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 09/42] Activate the VM Planes through the Hypervisor - Using KVM as the VMM
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (7 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 08/42] Add plane config param to specify kernel image format Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 10/42] allow the command line to be specified for kernels in other planes Sriram Nambakam
` (32 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
arch/x86/include/asm/cpu.h | 3 +-
arch/x86/kernel/cpu/common.c | 39 ++++++--
include/linux/vm_planes.h | 4 +-
init/vm_planes.c | 179 ++++++++++++++++++++++++++++++++++-
4 files changed, 212 insertions(+), 13 deletions(-)
diff --git a/arch/x86/include/asm/cpu.h b/arch/x86/include/asm/cpu.h
index 8ab76adf14a9..52e80c6ac8f0 100644
--- a/arch/x86/include/asm/cpu.h
+++ b/arch/x86/include/asm/cpu.h
@@ -13,8 +13,9 @@
#ifdef CONFIG_VM_PLANES
struct vm_plane_config;
-void __init alloc_vm_planes(unsigned int plane_count,
+int __init alloc_vm_planes(unsigned int plane_count,
struct vm_plane_config *plane_cfg);
+int __init activate_vm_planes(unsigned int plane_count);
#endif
#ifndef CONFIG_SMP
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index 166597204739..9912208d2010 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -82,7 +82,9 @@
#ifdef CONFIG_VM_PLANES
/* Private hypercall number for early VM plane configuration. */
-#define KVM_HC_VM_PLANES_CONFIG 0x1000
+#define KVM_HC_VM_PLANES_CONFIG 0x1000
+/* Private hypercall number to activate all configured planes. */
+#define KVM_HC_VM_PLANES_ACTIVATE 0x1001
#endif
DEFINE_PER_CPU_READ_MOSTLY(struct cpuinfo_x86, cpu_info);
@@ -2674,31 +2676,56 @@ void __init arch_cpu_finalize_init(void)
}
#ifdef CONFIG_VM_PLANES
-void __init alloc_vm_planes(unsigned int plane_count,
+int __init alloc_vm_planes(unsigned int plane_count,
struct vm_plane_config *plane_cfg)
{
phys_addr_t phys;
long ret;
if (!plane_count || !plane_cfg)
- return;
+ return -EINVAL;
if (!kvm_para_available()) {
pr_warn("vm_planes: hypercall interface unavailable\n");
- return;
+ return -ENODEV;
}
phys = virt_to_phys((void *)plane_cfg);
if (sizeof(unsigned long) < sizeof(phys_addr_t) && phys > ULONG_MAX) {
pr_warn("vm_planes: shared config address exceeds hypercall register width\n");
- return;
+ return -EOVERFLOW;
}
ret = kvm_hypercall2(KVM_HC_VM_PLANES_CONFIG,
(unsigned long)phys,
plane_count);
- if (ret < 0)
+ if (ret < 0) {
pr_warn("vm_planes: hypercall failed: %ld\n", ret);
+ return (int)ret;
+ }
+
+ return 0;
+}
+
+int __init activate_vm_planes(unsigned int plane_count)
+{
+ long ret;
+
+ if (!plane_count)
+ return -EINVAL;
+
+ if (!kvm_para_available()) {
+ pr_warn("vm_planes: hypercall interface unavailable\n");
+ return -ENODEV;
+ }
+
+ ret = kvm_hypercall1(KVM_HC_VM_PLANES_ACTIVATE, plane_count);
+ if (ret < 0) {
+ pr_warn("vm_planes: activate hypercall failed: %ld\n", ret);
+ return (int)ret;
+ }
+
+ return 0;
}
#endif
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index 6d0066f70349..5850c9e0d097 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -24,8 +24,8 @@ struct vm_plane_config {
};
void __init arch_init_vm_planes(void);
-void __init load_vm_plane_kernels(unsigned int plane_count,
- struct vm_plane_config *plane_cfg);
+int __init load_vm_plane_kernels(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
#endif /* CONFIG_VM_PLANES */
diff --git a/init/vm_planes.c b/init/vm_planes.c
index da9c17de4a44..6fac52af4b77 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -11,7 +11,7 @@
#include <linux/elf.h>
#include <asm/cpu.h>
#include <asm/kvm_para.h>
-#include <asm/io.h>
+#include <asm-generic/early_ioremap.h>
#ifdef CONFIG_VM_PLANES
static bool __initdata enable_vm_planes_requested;
@@ -405,6 +405,159 @@ static int __init vm_planes_get_cfg_from_initrd(unsigned int *plane_count,
return -ENOENT;
}
+static int __init find_initrd_file(const char *filename,
+ const u8 **out_data, u32 *out_size)
+{
+ const u8 *p = (const u8 *)(unsigned long)initrd_start;
+ const u8 *end = (const u8 *)(unsigned long)initrd_end;
+
+ if (!initrd_start || !initrd_end || initrd_end <= initrd_start)
+ return -ENOENT;
+
+ while (p + sizeof(struct cpio_newc_header) <= end) {
+ const struct cpio_newc_header *hdr;
+ const char *name;
+ const u8 *data;
+ u32 namesize, filesize;
+ u32 name_align, data_align;
+ int ret;
+
+ hdr = (const struct cpio_newc_header *)p;
+ if (memcmp(hdr->c_magic, "070701", 6) &&
+ memcmp(hdr->c_magic, "070702", 6))
+ return -EINVAL;
+
+ ret = parse_hex_field(hdr->c_namesize,
+ sizeof(hdr->c_namesize), &namesize);
+ if (ret)
+ return ret;
+
+ ret = parse_hex_field(hdr->c_filesize,
+ sizeof(hdr->c_filesize), &filesize);
+ if (ret)
+ return ret;
+
+ if (!namesize)
+ return -EINVAL;
+
+ p += sizeof(*hdr);
+ if (p + namesize > end)
+ return -EINVAL;
+
+ name = (const char *)p;
+ name_align = ALIGN(namesize, 4);
+ if (p + name_align > end)
+ return -EINVAL;
+
+ data = p + name_align;
+ if (data + filesize > end)
+ return -EINVAL;
+
+ if (!strcmp(name, "TRAILER!!!"))
+ break;
+
+ if (cpio_name_match(name, namesize, filename)) {
+ *out_data = data;
+ *out_size = filesize;
+ return 0;
+ }
+
+ data_align = ALIGN(filesize, 4);
+ if (data + data_align < data || data + data_align > end)
+ return -EINVAL;
+
+ p = data + data_align;
+ }
+
+ return -ENOENT;
+}
+
+static int __init copy_to_early_mem(phys_addr_t dest, const void *src,
+ unsigned long size)
+{
+ unsigned long slop, clen;
+ char *p;
+
+ while (size) {
+ slop = offset_in_page(dest);
+ clen = size;
+ if (clen > PAGE_SIZE - slop)
+ clen = PAGE_SIZE - slop;
+ p = early_memremap(dest & PAGE_MASK, clen + slop);
+ if (!p)
+ return -ENOMEM;
+ memcpy(p + slop, src, clen);
+ early_memunmap(p, clen + slop);
+ dest += clen;
+ src += clen;
+ size -= clen;
+ }
+ return 0;
+}
+
+static int __init load_plane_kernel_raw(const u8 *data, u32 size,
+ struct vm_plane_config *cfg)
+{
+ if (size > cfg->memory_size) {
+ pr_err("vm_planes: raw kernel image (%u bytes) exceeds plane memory (%llu bytes)\n",
+ size, (unsigned long long)cfg->memory_size);
+ return -ENOMEM;
+ }
+
+ return copy_to_early_mem(cfg->load_offset, data, size);
+}
+
+int __init load_vm_plane_kernels(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg)
+{
+ unsigned int i;
+ int err = 0;
+
+ for (i = 1; i < plane_count; i++) {
+ const u8 *data;
+ u32 size;
+ int ret;
+
+ ret = find_initrd_file(plane_cfg[i].kernel, &data, &size);
+ if (ret) {
+ pr_err("vm_planes: plane %u: kernel image '%s' not found in initrd\n",
+ i, plane_cfg[i].kernel);
+ err = ret;
+ continue;
+ }
+
+ switch (plane_cfg[i].kernel_format) {
+ case VM_PLANE_KFMT_RAW:
+ ret = load_plane_kernel_raw(data, size,
+ &plane_cfg[i]);
+ break;
+ case VM_PLANE_KFMT_BZIMAGE:
+ case VM_PLANE_KFMT_ELF:
+ pr_err("vm_planes: plane %u: kernel format not yet supported\n",
+ i);
+ err = -ENOSYS;
+ continue;
+ default:
+ pr_err("vm_planes: plane %u: unknown kernel format %u\n",
+ i, plane_cfg[i].kernel_format);
+ err = -EINVAL;
+ continue;
+ }
+
+ if (ret) {
+ pr_err("vm_planes: plane %u: failed to load kernel image: %d\n",
+ i, ret);
+ err = ret;
+ } else {
+ pr_info("vm_planes: plane %u: loaded '%s' (%u bytes) at 0x%llx\n",
+ i, plane_cfg[i].kernel,
+ size, (unsigned long long)plane_cfg[i].load_offset);
+ }
+ }
+
+ return err;
+}
+
static int __init parse_enable_vm_planes(char *str)
{
bool enable;
@@ -423,13 +576,16 @@ static int __init parse_enable_vm_planes(char *str)
early_param("enable-vm-planes", parse_enable_vm_planes);
-void __init __weak alloc_vm_planes(unsigned int plane_count,
- struct vm_plane_config *plane_cfg) { }
+int __init __weak alloc_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg) { return -ENOSYS; }
+
+int __init __weak activate_vm_planes(unsigned int plane_count) { return -ENOSYS; }
void __init arch_init_vm_planes(void)
{
unsigned int plane_count = VM_PLANES_DEFAULT_COUNT;
struct vm_plane_config *plane_cfg;
+ int ret;
if (!enable_vm_planes_requested)
return;
@@ -445,7 +601,22 @@ void __init arch_init_vm_planes(void)
pr_info("vm_planes: enabling %u planes (ids 0..%u)\n",
plane_count, plane_count - 1);
- alloc_vm_planes(plane_count, plane_cfg);
+
+ ret = alloc_vm_planes(plane_count, plane_cfg);
+ if (ret) {
+ pr_err("vm_planes: failed to allocate planes: %d\n", ret);
+ return;
+ }
+
+ ret = load_vm_plane_kernels(plane_count, plane_cfg);
+ if (ret) {
+ pr_err("vm_planes: failed to load plane kernels: %d\n", ret);
+ return;
+ }
+
+ ret = activate_vm_planes(plane_count);
+ if (ret)
+ pr_err("vm_planes: failed to activate planes: %d\n", ret);
}
#endif /* CONFIG_VM_PLANES */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 10/42] allow the command line to be specified for kernels in other planes
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (8 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 09/42] Activate the VM Planes through the Hypervisor - Using KVM as the VMM Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 11/42] Various changes to support VM Planes Sriram Nambakam
` (31 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
include/linux/vm_planes.h | 2 ++
init/vm_planes.c | 23 ++++++++++++++++++++++-
2 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index 5850c9e0d097..bb06dcbcf0cb 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -8,6 +8,7 @@
#ifdef CONFIG_VM_PLANES
#define VM_PLANE_KERNEL_NAME_MAX 128
+#define VM_PLANE_CMDLINE_MAX 512
enum vm_plane_kernel_format {
VM_PLANE_KFMT_RAW = 0,
@@ -21,6 +22,7 @@ struct vm_plane_config {
unsigned int vcpu_count;
unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
+ char cmdline[VM_PLANE_CMDLINE_MAX];
};
void __init arch_init_vm_planes(void);
diff --git a/init/vm_planes.c b/init/vm_planes.c
index 6fac52af4b77..613daa161298 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -25,6 +25,7 @@ struct vm_plane_parse_state {
unsigned int vcpu_count;
unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
+ char cmdline[VM_PLANE_CMDLINE_MAX];
};
#define VM_PLANES_UNSET_VALUE ((phys_addr_t)~0)
@@ -141,7 +142,7 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
struct vm_plane_config *plane_cfg,
struct vm_plane_parse_state *state)
{
- char tmp[192];
+ char tmp[VM_PLANE_CMDLINE_MAX + 64];
char *p, *key, *val;
unsigned int plane_id;
u64 parsed_u64;
@@ -229,6 +230,25 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
return 0;
}
+ if (!strcmp(key, "CMDLINE")) {
+ size_t val_len = strlen(val);
+
+ if (val_len >= 2 && val[0] == '"') {
+ if (val[val_len - 1] != '"')
+ return -EINVAL;
+ val[val_len - 1] = '\0';
+ val++;
+ }
+
+ if (strscpy(plane_cfg[plane_id].cmdline, val,
+ sizeof(plane_cfg[plane_id].cmdline)) < 0)
+ return -E2BIG;
+
+ strscpy(state[plane_id].cmdline, val,
+ sizeof(state[plane_id].cmdline));
+ return 0;
+ }
+
if (kstrtou64(val, 0, &parsed_u64))
return -EINVAL;
@@ -292,6 +312,7 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
state[i].memory_size = VM_PLANES_UNSET_VALUE;
state[i].vcpu_count = 0;
state[i].kernel[0] = '\0';
+ state[i].cmdline[0] = '\0';
}
while (p < end) {
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 11/42] Various changes to support VM Planes.
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (9 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 10/42] allow the command line to be specified for kernels in other planes Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 12/42] Add a Virtualization Based Security (VBS) framework. - Add backends for AMD SEV-SNP, Intel TDX, Arm CCA and KVM Planes. - Support VTL on Hyper-V in addition to Planes on KVM Sriram Nambakam
` (30 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Remove custom parsing of initrd.
1. **vm_planes.c** — Core VM planes implementation (major rewrite)
- Fixed config parser: `PLANE_COUNT=` line no longer causes fatal `-EINVAL` (returns `-ENOENT` to skip)
- ELF loader: biases `p_paddr` by `load_offset` so kernel loads at correct GPA
- Entry point: computes physical entry from ELF vaddr→paddr mapping, with fallback for physical `e_entry`
- `activate_vm_planes()` now passes `plane_cfg` GPA so QEMU can read the updated `entry_point`
2. **vm_planes.h** — Added `entry_point` field declaration
3. **main.c** — Minor adjustment to `arch_init_vm_planes()` call site
4. **common.c** — KVM hypercall implementations
- Removed hardcoded `0x1000`/`0x1001` hypercall numbers
- `alloc_vm_planes()`: unchanged (uses correct HC numbers from UAPI header)
- `activate_vm_planes()`: now passes `plane_cfg` GPA + `plane_count` (was just `plane_count`)
5. **cpu.h** — Updated `activate_vm_planes()` signature to include `plane_cfg`
6. **x86.c** — KVM host-side hypercall support
- `KVM_EXIT_HYPERCALL_VALID_MASK`: added bits 13 and 14 for VM planes hypercalls
- Added `KVM_HC_VM_PLANES_CONFIG` and `KVM_HC_VM_PLANES_ACTIVATE` case handlers that exit to userspace (QEMU)
7. **kvm_para.h** — Added hypercall numbers
- `KVM_HC_VM_PLANES_CONFIG = 13`
- `KVM_HC_VM_PLANES_ACTIVATE = 14`
---
arch/x86/include/asm/cpu.h | 3 +-
arch/x86/kernel/cpu/common.c | 19 +-
arch/x86/kvm/x86.c | 25 +-
include/linux/vm_planes.h | 1 +
include/uapi/linux/kvm_para.h | 2 +
init/main.c | 7 +-
init/vm_planes.c | 422 ++++++++++++++++++----------------
7 files changed, 266 insertions(+), 213 deletions(-)
diff --git a/arch/x86/include/asm/cpu.h b/arch/x86/include/asm/cpu.h
index 52e80c6ac8f0..f9cb541e6367 100644
--- a/arch/x86/include/asm/cpu.h
+++ b/arch/x86/include/asm/cpu.h
@@ -15,7 +15,8 @@ struct vm_plane_config;
int __init alloc_vm_planes(unsigned int plane_count,
struct vm_plane_config *plane_cfg);
-int __init activate_vm_planes(unsigned int plane_count);
+int __init activate_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
#endif
#ifndef CONFIG_SMP
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index 9912208d2010..7edc2c4072cc 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -80,13 +80,6 @@
#include "cpu.h"
-#ifdef CONFIG_VM_PLANES
-/* Private hypercall number for early VM plane configuration. */
-#define KVM_HC_VM_PLANES_CONFIG 0x1000
-/* Private hypercall number to activate all configured planes. */
-#define KVM_HC_VM_PLANES_ACTIVATE 0x1001
-#endif
-
DEFINE_PER_CPU_READ_MOSTLY(struct cpuinfo_x86, cpu_info);
EXPORT_PER_CPU_SYMBOL(cpu_info);
@@ -2708,11 +2701,13 @@ int __init alloc_vm_planes(unsigned int plane_count,
return 0;
}
-int __init activate_vm_planes(unsigned int plane_count)
+int __init activate_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg)
{
+ phys_addr_t phys;
long ret;
- if (!plane_count)
+ if (!plane_count || !plane_cfg)
return -EINVAL;
if (!kvm_para_available()) {
@@ -2720,7 +2715,11 @@ int __init activate_vm_planes(unsigned int plane_count)
return -ENODEV;
}
- ret = kvm_hypercall1(KVM_HC_VM_PLANES_ACTIVATE, plane_count);
+ phys = virt_to_phys((void *)plane_cfg);
+
+ ret = kvm_hypercall2(KVM_HC_VM_PLANES_ACTIVATE,
+ (unsigned long)phys,
+ plane_count);
if (ret < 0) {
pr_warn("vm_planes: activate hypercall failed: %ld\n", ret);
return (int)ret;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index a0a8818b3096..b7256f155bea 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -119,7 +119,9 @@ u64 __read_mostly efer_reserved_bits = ~((u64)(EFER_SCE | EFER_LME | EFER_LMA));
static u64 __read_mostly efer_reserved_bits = ~((u64)EFER_SCE);
#endif
-#define KVM_EXIT_HYPERCALL_VALID_MASK (1 << KVM_HC_MAP_GPA_RANGE)
+#define KVM_EXIT_HYPERCALL_VALID_MASK (BIT(KVM_HC_MAP_GPA_RANGE) | \
+ BIT(KVM_HC_VM_PLANES_CONFIG) | \
+ BIT(KVM_HC_VM_PLANES_ACTIVATE))
#define KVM_CAP_PMU_VALID_MASK KVM_PMU_CAP_DISABLE
@@ -10530,6 +10532,27 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
vcpu->arch.complete_userspace_io = complete_hypercall;
return 0;
}
+ case KVM_HC_VM_PLANES_CONFIG:
+ case KVM_HC_VM_PLANES_ACTIVATE: {
+ ret = -KVM_ENOSYS;
+ if (!user_exit_on_hypercall(vcpu->kvm, nr))
+ break;
+
+ vcpu->run->exit_reason = KVM_EXIT_HYPERCALL;
+ vcpu->run->hypercall.nr = nr;
+ vcpu->run->hypercall.ret = 0;
+ vcpu->run->hypercall.args[0] = a0;
+ vcpu->run->hypercall.args[1] = a1;
+ vcpu->run->hypercall.args[2] = a2;
+ vcpu->run->hypercall.args[3] = a3;
+ vcpu->run->hypercall.flags = 0;
+ if (op_64_bit)
+ vcpu->run->hypercall.flags |= KVM_EXIT_HYPERCALL_LONG_MODE;
+
+ WARN_ON_ONCE(vcpu->run->hypercall.flags & KVM_EXIT_HYPERCALL_MBZ);
+ vcpu->arch.complete_userspace_io = complete_hypercall;
+ return 0;
+ }
default:
ret = -KVM_ENOSYS;
break;
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index bb06dcbcf0cb..47f05fa80039 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -19,6 +19,7 @@ enum vm_plane_kernel_format {
struct vm_plane_config {
phys_addr_t load_offset;
phys_addr_t memory_size;
+ phys_addr_t entry_point;
unsigned int vcpu_count;
unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 960c7e93d1a9..1b097f7ed937 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -30,6 +30,8 @@
#define KVM_HC_SEND_IPI 10
#define KVM_HC_SCHED_YIELD 11
#define KVM_HC_MAP_GPA_RANGE 12
+#define KVM_HC_VM_PLANES_CONFIG 13
+#define KVM_HC_VM_PLANES_ACTIVATE 14
/*
* hypercalls use architecture specific
diff --git a/init/main.c b/init/main.c
index 3e35c2caca17..1c779f6d60cc 100644
--- a/init/main.c
+++ b/init/main.c
@@ -994,9 +994,6 @@ void start_kernel(void)
pr_notice("%s", linux_banner);
setup_arch(&command_line);
mm_core_init_early();
-#ifdef CONFIG_VM_PLANES
- arch_init_vm_planes();
-#endif
/* Static keys and static calls are needed by LSMs */
jump_label_init();
static_call_init();
@@ -1666,6 +1663,10 @@ static noinline void __init kernel_init_freeable(void)
wait_for_initramfs();
console_on_rootfs();
+#ifdef CONFIG_VM_PLANES
+ arch_init_vm_planes();
+#endif
+
/*
* check if there is an early userspace init. If yes, let it do all
* the work
diff --git a/init/vm_planes.c b/init/vm_planes.c
index 613daa161298..274c0015fe76 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -3,12 +3,15 @@
#include <linux/init.h>
#include <linux/initrd.h>
#include <linux/kernel.h>
-#include <linux/memblock.h>
+#include <linux/slab.h>
#include <linux/kstrtox.h>
#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/file.h>
#include <linux/kvm_para.h>
#include <linux/vm_planes.h>
#include <linux/elf.h>
+#include <linux/mm.h>
#include <asm/cpu.h>
#include <asm/kvm_para.h>
#include <asm-generic/early_ioremap.h>
@@ -30,46 +33,49 @@ struct vm_plane_parse_state {
#define VM_PLANES_UNSET_VALUE ((phys_addr_t)~0)
-struct cpio_newc_header {
- char c_magic[6];
- char c_ino[8];
- char c_mode[8];
- char c_uid[8];
- char c_gid[8];
- char c_nlink[8];
- char c_mtime[8];
- char c_filesize[8];
- char c_devmajor[8];
- char c_devminor[8];
- char c_rdevmajor[8];
- char c_rdevminor[8];
- char c_namesize[8];
- char c_check[8];
-};
-
-static int __init parse_hex_field(const char *field, size_t len, u32 *value)
+/*
+ * Read a file from the rootfs into a newly allocated buffer.
+ * Caller must kfree(*out_data) when done.
+ */
+static int __init vm_planes_read_file(const char *path,
+ void **out_data, loff_t *out_size)
{
- u32 v = 0;
- size_t i;
-
- for (i = 0; i < len; i++) {
- u8 c = field[i];
-
- v <<= 4;
- if (c >= '0' && c <= '9')
- v |= c - '0';
- else if (c >= 'a' && c <= 'f')
- v |= c - 'a' + 10;
- else if (c >= 'A' && c <= 'F')
- v |= c - 'A' + 10;
- else
- return -EINVAL;
+ struct file *fp;
+ loff_t fsize;
+ void *buf;
+ ssize_t rd;
+
+ fp = filp_open(path, O_RDONLY, 0);
+ if (IS_ERR(fp))
+ return PTR_ERR(fp);
+
+ fsize = i_size_read(file_inode(fp));
+ if (fsize <= 0) {
+ fput(fp);
+ return -ENODATA;
+ }
+
+ buf = kvmalloc(fsize, GFP_KERNEL);
+ if (!buf) {
+ fput(fp);
+ return -ENOMEM;
+ }
+
+ rd = kernel_read(fp, buf, fsize, &(loff_t){0});
+ fput(fp);
+
+ if (rd != fsize) {
+ kvfree(buf);
+ return (rd < 0) ? (int)rd : -EIO;
}
- *value = v;
+ *out_data = buf;
+ *out_size = fsize;
return 0;
}
+/* ---- Config file parser (unchanged) ---- */
+
static int __init parse_plane_count_line(const char *line, size_t len,
unsigned int *plane_count)
{
@@ -174,7 +180,7 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
key = strchr(p, '_');
if (!key)
- return -EINVAL;
+ return -ENOENT;
*key++ = '\0';
if (kstrtouint(p, 10, &plane_id) || plane_id >= plane_count)
@@ -297,16 +303,14 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
if (*plane_count > UINT_MAX / sizeof(**plane_cfg))
return -E2BIG;
- *plane_cfg = memblock_alloc(*plane_count * sizeof(**plane_cfg),
- SMP_CACHE_BYTES);
+ *plane_cfg = kzalloc(*plane_count * sizeof(**plane_cfg), GFP_KERNEL);
if (!*plane_cfg)
return -ENOMEM;
- state = memblock_alloc(*plane_count * sizeof(*state), SMP_CACHE_BYTES);
+ state = kzalloc(*plane_count * sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
- memset(*plane_cfg, 0, *plane_count * sizeof(**plane_cfg));
for (i = 0; i < *plane_count; i++) {
state[i].load_offset = VM_PLANES_UNSET_VALUE;
state[i].memory_size = VM_PLANES_UNSET_VALUE;
@@ -329,9 +333,6 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
p++;
}
- /* Plane 0 is the already-running boot plane; only secondary planes
- * must provide full allocation metadata.
- */
for (i = 1; i < *plane_count; i++) {
if (state[i].load_offset == VM_PLANES_UNSET_VALUE ||
state[i].memory_size == VM_PLANES_UNSET_VALUE ||
@@ -340,179 +341,192 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
return -EINVAL;
}
+ kfree(state);
return 0;
}
-static bool __init cpio_name_match(const char *name, size_t namesize,
- const char *target)
-{
- while (namesize > 1 && (*name == '/' ||
- (namesize > 2 && name[0] == '.' && name[1] == '/'))) {
- if (*name == '/') {
- name++;
- namesize--;
- } else {
- name += 2;
- namesize -= 2;
- }
- }
+/* ---- Config loading via VFS ---- */
- return !strncmp(name, target, namesize - 1) &&
- strlen(target) == namesize - 1;
-}
-
-static int __init vm_planes_get_cfg_from_initrd(unsigned int *plane_count,
- struct vm_plane_config **plane_cfg)
+static int __init vm_planes_get_cfg(unsigned int *plane_count,
+ struct vm_plane_config **plane_cfg)
{
- const u8 *p = (const u8 *)(unsigned long)initrd_start;
- const u8 *end = (const u8 *)(unsigned long)initrd_end;
-
- if (!initrd_start || !initrd_end || initrd_end <= initrd_start)
- return -ENOENT;
-
- while (p + sizeof(struct cpio_newc_header) <= end) {
- const struct cpio_newc_header *hdr;
- const char *name;
- const u8 *data;
- u32 namesize, filesize;
- u32 name_align, data_align;
- int ret;
-
- hdr = (const struct cpio_newc_header *)p;
- if (memcmp(hdr->c_magic, "070701", 6) &&
- memcmp(hdr->c_magic, "070702", 6))
- return -EINVAL;
-
- ret = parse_hex_field(hdr->c_namesize, sizeof(hdr->c_namesize), &namesize);
- if (ret)
- return ret;
-
- ret = parse_hex_field(hdr->c_filesize, sizeof(hdr->c_filesize), &filesize);
- if (ret)
- return ret;
-
- if (!namesize)
- return -EINVAL;
+ void *buf;
+ loff_t size;
+ int ret;
- p += sizeof(*hdr);
- if (p + namesize > end)
- return -EINVAL;
+ ret = vm_planes_read_file("/" VM_PLANES_CONFIG_FILE, &buf, &size);
+ if (ret) {
+ pr_err("vm_planes: cannot read /%s: %d\n",
+ VM_PLANES_CONFIG_FILE, ret);
+ return ret;
+ }
- name = (const char *)p;
- name_align = ALIGN(namesize, 4);
- if (p + name_align > end)
- return -EINVAL;
+ ret = parse_vm_planes_kconfig(buf, (size_t)size, plane_count, plane_cfg);
+ kvfree(buf);
+ return ret;
+}
- data = p + name_align;
- if (data + filesize > end)
- return -EINVAL;
+/* ---- Kernel loading ---- */
- if (!strcmp(name, "TRAILER!!!"))
- break;
+static int __init copy_to_early_mem(phys_addr_t dest, const void *src,
+ unsigned long size)
+{
+ unsigned long slop, clen;
+ char *p;
- if (cpio_name_match(name, namesize, VM_PLANES_CONFIG_FILE))
- return parse_vm_planes_kconfig((const char *)data,
- filesize,
- plane_count,
- plane_cfg);
+ while (size) {
+ slop = offset_in_page(dest);
+ clen = size;
+ if (clen > PAGE_SIZE - slop)
+ clen = PAGE_SIZE - slop;
+ p = early_memremap(dest & PAGE_MASK, clen + slop);
+ if (!p)
+ return -ENOMEM;
+ memcpy(p + slop, src, clen);
+ early_memunmap(p, clen + slop);
+ dest += clen;
+ src += clen;
+ size -= clen;
+ }
+ return 0;
+}
- data_align = ALIGN(filesize, 4);
- if (data + data_align < data || data + data_align > end)
- return -EINVAL;
+static int __init zero_early_mem(phys_addr_t dest, unsigned long size)
+{
+ unsigned long slop, clen;
+ char *p;
- p = data + data_align;
+ while (size) {
+ slop = offset_in_page(dest);
+ clen = size;
+ if (clen > PAGE_SIZE - slop)
+ clen = PAGE_SIZE - slop;
+ p = early_memremap(dest & PAGE_MASK, clen + slop);
+ if (!p)
+ return -ENOMEM;
+ memset(p + slop, 0, clen);
+ early_memunmap(p, clen + slop);
+ dest += clen;
+ size -= clen;
}
-
- return -ENOENT;
+ return 0;
}
-static int __init find_initrd_file(const char *filename,
- const u8 **out_data, u32 *out_size)
+static int __init load_plane_kernel_elf(const u8 *data, u32 size,
+ struct vm_plane_config *cfg)
{
- const u8 *p = (const u8 *)(unsigned long)initrd_start;
- const u8 *end = (const u8 *)(unsigned long)initrd_end;
+ const Elf64_Ehdr *ehdr;
+ const Elf64_Phdr *phdr;
+ unsigned int i;
+ int ret;
- if (!initrd_start || !initrd_end || initrd_end <= initrd_start)
- return -ENOENT;
+ if (size < sizeof(*ehdr)) {
+ pr_err("vm_planes: ELF image too small (%u bytes)\n", size);
+ return -EINVAL;
+ }
- while (p + sizeof(struct cpio_newc_header) <= end) {
- const struct cpio_newc_header *hdr;
- const char *name;
- const u8 *data;
- u32 namesize, filesize;
- u32 name_align, data_align;
- int ret;
+ ehdr = (const Elf64_Ehdr *)data;
- hdr = (const struct cpio_newc_header *)p;
- if (memcmp(hdr->c_magic, "070701", 6) &&
- memcmp(hdr->c_magic, "070702", 6))
- return -EINVAL;
+ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+ pr_err("vm_planes: not a valid ELF image\n");
+ return -EINVAL;
+ }
- ret = parse_hex_field(hdr->c_namesize,
- sizeof(hdr->c_namesize), &namesize);
- if (ret)
- return ret;
+ if (ehdr->e_ident[EI_CLASS] != ELFCLASS64 ||
+ ehdr->e_ident[EI_DATA] != ELFDATA2LSB ||
+ ehdr->e_type != ET_EXEC ||
+ ehdr->e_machine != EM_X86_64) {
+ pr_err("vm_planes: unsupported ELF format (need x86_64 ET_EXEC LE)\n");
+ return -EINVAL;
+ }
- ret = parse_hex_field(hdr->c_filesize,
- sizeof(hdr->c_filesize), &filesize);
- if (ret)
- return ret;
+ if (!ehdr->e_phnum || ehdr->e_phentsize != sizeof(Elf64_Phdr)) {
+ pr_err("vm_planes: invalid ELF program headers\n");
+ return -EINVAL;
+ }
- if (!namesize)
- return -EINVAL;
+ if (ehdr->e_phoff + (u64)ehdr->e_phnum * sizeof(Elf64_Phdr) > size) {
+ pr_err("vm_planes: ELF program headers extend beyond file\n");
+ return -EINVAL;
+ }
- p += sizeof(*hdr);
- if (p + namesize > end)
- return -EINVAL;
+ phdr = (const Elf64_Phdr *)(data + ehdr->e_phoff);
- name = (const char *)p;
- name_align = ALIGN(namesize, 4);
- if (p + name_align > end)
- return -EINVAL;
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ phys_addr_t dest;
+ u64 bss_size;
- data = p + name_align;
- if (data + filesize > end)
- return -EINVAL;
+ if (phdr->p_type != PT_LOAD)
+ continue;
- if (!strcmp(name, "TRAILER!!!"))
- break;
+ if (!phdr->p_memsz)
+ continue;
- if (cpio_name_match(name, namesize, filename)) {
- *out_data = data;
- *out_size = filesize;
- return 0;
+ /*
+ * Bias the ELF physical address by load_offset so that the
+ * kernel's link-time p_paddr values are treated as offsets
+ * within the plane's memory region.
+ */
+ dest = cfg->load_offset + phdr->p_paddr;
+
+ if (dest < cfg->load_offset ||
+ dest + phdr->p_memsz > cfg->load_offset + cfg->memory_size) {
+ pr_err("vm_planes: ELF PT_LOAD at 0x%llx+0x%llx outside plane [0x%llx..0x%llx]\n",
+ (unsigned long long)dest,
+ (unsigned long long)phdr->p_memsz,
+ (unsigned long long)cfg->load_offset,
+ (unsigned long long)(cfg->load_offset + cfg->memory_size));
+ return -EINVAL;
}
- data_align = ALIGN(filesize, 4);
- if (data + data_align < data || data + data_align > end)
+ if (phdr->p_offset + phdr->p_filesz > size) {
+ pr_err("vm_planes: ELF PT_LOAD file data beyond image\n");
return -EINVAL;
+ }
- p = data + data_align;
- }
+ if (phdr->p_filesz) {
+ ret = copy_to_early_mem(dest, data + phdr->p_offset,
+ phdr->p_filesz);
+ if (ret)
+ return ret;
+ }
- return -ENOENT;
-}
+ bss_size = phdr->p_memsz - phdr->p_filesz;
+ if (bss_size) {
+ ret = zero_early_mem(dest + phdr->p_filesz, bss_size);
+ if (ret)
+ return ret;
+ }
-static int __init copy_to_early_mem(phys_addr_t dest, const void *src,
- unsigned long size)
-{
- unsigned long slop, clen;
- char *p;
+ /*
+ * Compute the physical entry point: if e_entry falls within
+ * this segment's virtual range, convert vaddr→paddr and bias.
+ * Also handle kernels where e_entry is already a physical
+ * address by checking the p_paddr range as a fallback.
+ */
+ if (ehdr->e_entry >= phdr->p_vaddr &&
+ ehdr->e_entry < phdr->p_vaddr + phdr->p_memsz)
+ cfg->entry_point = cfg->load_offset +
+ phdr->p_paddr + (ehdr->e_entry - phdr->p_vaddr);
+ else if (ehdr->e_entry >= phdr->p_paddr &&
+ ehdr->e_entry < phdr->p_paddr + phdr->p_memsz)
+ cfg->entry_point = cfg->load_offset + ehdr->e_entry;
+
+ pr_info("vm_planes: ELF PT_LOAD: paddr=0x%llx filesz=0x%llx memsz=0x%llx\n",
+ (unsigned long long)dest,
+ (unsigned long long)phdr->p_filesz,
+ (unsigned long long)phdr->p_memsz);
+ }
- while (size) {
- slop = offset_in_page(dest);
- clen = size;
- if (clen > PAGE_SIZE - slop)
- clen = PAGE_SIZE - slop;
- p = early_memremap(dest & PAGE_MASK, clen + slop);
- if (!p)
- return -ENOMEM;
- memcpy(p + slop, src, clen);
- early_memunmap(p, clen + slop);
- dest += clen;
- src += clen;
- size -= clen;
+ if (!cfg->entry_point) {
+ pr_err("vm_planes: ELF entry point 0x%llx not in any PT_LOAD segment\n",
+ (unsigned long long)ehdr->e_entry);
+ return -EINVAL;
}
+ pr_info("vm_planes: ELF entry point: 0x%llx (virt 0x%llx)\n",
+ (unsigned long long)cfg->entry_point,
+ (unsigned long long)ehdr->e_entry);
+
return 0;
}
@@ -525,6 +539,7 @@ static int __init load_plane_kernel_raw(const u8 *data, u32 size,
return -ENOMEM;
}
+ cfg->entry_point = cfg->load_offset;
return copy_to_early_mem(cfg->load_offset, data, size);
}
@@ -535,50 +550,59 @@ int __init load_vm_plane_kernels(unsigned int plane_count,
int err = 0;
for (i = 1; i < plane_count; i++) {
- const u8 *data;
- u32 size;
+ void *data;
+ loff_t fsize;
int ret;
- ret = find_initrd_file(plane_cfg[i].kernel, &data, &size);
+ ret = vm_planes_read_file(plane_cfg[i].kernel, &data, &fsize);
if (ret) {
- pr_err("vm_planes: plane %u: kernel image '%s' not found in initrd\n",
- i, plane_cfg[i].kernel);
+ pr_err("vm_planes: plane %u: kernel '%s' not found: %d\n",
+ i, plane_cfg[i].kernel, ret);
err = ret;
continue;
}
switch (plane_cfg[i].kernel_format) {
case VM_PLANE_KFMT_RAW:
- ret = load_plane_kernel_raw(data, size,
+ ret = load_plane_kernel_raw(data, (u32)fsize,
&plane_cfg[i]);
break;
- case VM_PLANE_KFMT_BZIMAGE:
case VM_PLANE_KFMT_ELF:
- pr_err("vm_planes: plane %u: kernel format not yet supported\n",
+ ret = load_plane_kernel_elf(data, (u32)fsize,
+ &plane_cfg[i]);
+ break;
+ case VM_PLANE_KFMT_BZIMAGE:
+ pr_err("vm_planes: plane %u: bzImage format not yet supported\n",
i);
err = -ENOSYS;
+ kvfree(data);
continue;
default:
pr_err("vm_planes: plane %u: unknown kernel format %u\n",
i, plane_cfg[i].kernel_format);
err = -EINVAL;
+ kvfree(data);
continue;
}
if (ret) {
- pr_err("vm_planes: plane %u: failed to load kernel image: %d\n",
+ pr_err("vm_planes: plane %u: failed to load kernel: %d\n",
i, ret);
err = ret;
} else {
- pr_info("vm_planes: plane %u: loaded '%s' (%u bytes) at 0x%llx\n",
- i, plane_cfg[i].kernel,
- size, (unsigned long long)plane_cfg[i].load_offset);
+ pr_info("vm_planes: plane %u: loaded '%s' (%lld bytes) at 0x%llx\n",
+ i, plane_cfg[i].kernel, fsize,
+ (unsigned long long)plane_cfg[i].load_offset);
}
+
+ kvfree(data);
}
return err;
}
+/* ---- Early param & activation ---- */
+
static int __init parse_enable_vm_planes(char *str)
{
bool enable;
@@ -600,7 +624,8 @@ early_param("enable-vm-planes", parse_enable_vm_planes);
int __init __weak alloc_vm_planes(unsigned int plane_count,
struct vm_plane_config *plane_cfg) { return -ENOSYS; }
-int __init __weak activate_vm_planes(unsigned int plane_count) { return -ENOSYS; }
+int __init __weak activate_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg) { return -ENOSYS; }
void __init arch_init_vm_planes(void)
{
@@ -614,9 +639,10 @@ void __init arch_init_vm_planes(void)
if (!kvm_para_available())
return;
- if (vm_planes_get_cfg_from_initrd(&plane_count, &plane_cfg)) {
- pr_warn("vm_planes: failed to parse %s from initrd\n",
- VM_PLANES_CONFIG_FILE);
+ ret = vm_planes_get_cfg(&plane_count, &plane_cfg);
+ if (ret) {
+ pr_warn("vm_planes: failed to parse %s: %d\n",
+ VM_PLANES_CONFIG_FILE, ret);
return;
}
@@ -635,7 +661,7 @@ void __init arch_init_vm_planes(void)
return;
}
- ret = activate_vm_planes(plane_count);
+ ret = activate_vm_planes(plane_count, plane_cfg);
if (ret)
pr_err("vm_planes: failed to activate planes: %d\n", ret);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 12/42] Add a Virtualization Based Security (VBS) framework. - Add backends for AMD SEV-SNP, Intel TDX, Arm CCA and KVM Planes. - Support VTL on Hyper-V in addition to Planes on KVM.
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (10 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 11/42] Various changes to support VM Planes Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 13/42] Add a inter-plane communication mechanism through KVM. - model this to use a single page similar to SEV-SNP Sriram Nambakam
` (29 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
include/linux/vbs.h | 204 ++++++++++++++++++++++++++
security/Kconfig | 2 +
security/Makefile | 3 +
security/vbs/Kconfig | 69 +++++++++
security/vbs/Makefile | 9 ++
security/vbs/arm_cca.c | 300 ++++++++++++++++++++++++++++++++++++++
security/vbs/core.c | 166 +++++++++++++++++++++
security/vbs/hv_vsm.c | 257 ++++++++++++++++++++++++++++++++
security/vbs/internal.h | 41 ++++++
security/vbs/kvm_planes.c | 258 ++++++++++++++++++++++++++++++++
security/vbs/probe.c | 103 +++++++++++++
security/vbs/sev_snp.c | 224 ++++++++++++++++++++++++++++
security/vbs/tdx.c | 280 +++++++++++++++++++++++++++++++++++
13 files changed, 1916 insertions(+)
create mode 100644 include/linux/vbs.h
create mode 100644 security/vbs/Kconfig
create mode 100644 security/vbs/Makefile
create mode 100644 security/vbs/arm_cca.c
create mode 100644 security/vbs/core.c
create mode 100644 security/vbs/hv_vsm.c
create mode 100644 security/vbs/internal.h
create mode 100644 security/vbs/kvm_planes.c
create mode 100644 security/vbs/probe.c
create mode 100644 security/vbs/sev_snp.c
create mode 100644 security/vbs/tdx.c
diff --git a/include/linux/vbs.h b/include/linux/vbs.h
new file mode 100644
index 000000000000..c7dedb90d64c
--- /dev/null
+++ b/include/linux/vbs.h
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * VBS — Virtualization-Based Security
+ *
+ * Transport-agnostic interface between the guest OS (plane-0 / VTL0 / VMPL2+)
+ * and the secure kernel (plane-1 / VTL1 / VMPL0 / service TD).
+ *
+ * Backends:
+ * - KVM software planes (KVM_X86_DEFAULT_VM, QEMU-managed vCPU threads)
+ * - AMD SEV-SNP VMPL/SVSM (KVM_X86_SNP_VM, hardware VMPLs, SVSM protocol)
+ * - Intel TDX service TD (future — separate TD with shared memory)
+ * - Hyper-V VSM (native VTL hypercalls)
+ * - Arm CCA (RSI host calls from Realm guest to RMM)
+ *
+ * The guest kernel calls vbs_*() functions. The active backend translates
+ * them into the appropriate transport (hypercall, VMGEXIT, shared-memory IPC).
+ */
+
+#ifndef _LINUX_VBS_H
+#define _LINUX_VBS_H
+
+#include <linux/types.h>
+#include <linux/errno.h>
+
+struct module;
+
+/* ────────────────────────────────────────────────────────────────────────── */
+/* Memory protection flags */
+/* ────────────────────────────────────────────────────────────────────────── */
+
+/* Permissions that the secure kernel can enforce on lower-plane memory. */
+#define VBS_MEM_READ BIT(0)
+#define VBS_MEM_WRITE BIT(1)
+#define VBS_MEM_EXEC BIT(2)
+
+/* ────────────────────────────────────────────────────────────────────────── */
+/* VTL-call request codes (plane-0 → plane-1 direction) */
+/* ────────────────────────────────────────────────────────────────────────── */
+
+enum vbs_call_id {
+ /* Core lifecycle */
+ VBS_CALL_INIT = 0x0001, /* plane-0 boot complete */
+ VBS_CALL_SHUTDOWN = 0x0002, /* plane-0 shutting down */
+
+ /* Memory protection (HEKI) */
+ VBS_CALL_PROTECT_MEMORY = 0x0100, /* set page permissions */
+ VBS_CALL_SEAL_KERNEL = 0x0101, /* make kernel text immutable */
+
+ /* Module authentication */
+ VBS_CALL_VALIDATE_MODULE = 0x0200, /* verify module signature */
+ VBS_CALL_SET_MODULE_PERMS = 0x0201, /* set module section perms */
+ VBS_CALL_UNLOAD_MODULE = 0x0202, /* module being freed */
+
+ /* Key / certificate management */
+ VBS_CALL_ADD_KEY = 0x0300, /* add runtime key */
+ VBS_CALL_REVOKE_KEY = 0x0301, /* revoke a key */
+ VBS_CALL_SEND_CERTS = 0x0302, /* send system certificates */
+
+ /* Kexec validation */
+ VBS_CALL_KEXEC_VALIDATE = 0x0400, /* validate kexec kernel */
+ VBS_CALL_KEXEC_INVALIDATE = 0x0401, /* invalidate kexec state */
+};
+
+/* ────────────────────────────────────────────────────────────────────────── */
+/* Backend operations (one implementation per platform) */
+/* ────────────────────────────────────────────────────────────────────────── */
+
+/**
+ * struct vbs_ops - operations provided by an VBS backend
+ *
+ * All callbacks are optional; returning -ENOTSUP means the backend does
+ * not implement that feature. The core VBS layer will call these from
+ * process context with preemption enabled.
+ */
+struct vbs_ops {
+ const char *name; /* "kvm-planes", "svsm", "hv-vsm", … */
+
+ /*
+ * Lifecycle
+ */
+
+ /** @init: called once after plane-0 kernel boot is complete. */
+ int (*init)(void);
+
+ /** @shutdown: called before plane-0 halts/reboots. */
+ void (*shutdown)(void);
+
+ /*
+ * Raw VTL call — send an arbitrary request to the secure kernel
+ * and wait for a response. @id is the call code, @arg / @arg_size
+ * point to request-specific data, @resp / @resp_size receive the
+ * reply. Returns 0 on success, negative errno on failure.
+ */
+ int (*vtl_call)(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size);
+
+ /*
+ * Memory protection (HEKI)
+ *
+ * Ask the secure kernel to enforce @perms (VBS_MEM_*) on the
+ * physical page range [pfn, pfn + nr_pages) from the perspective
+ * of the lower plane.
+ */
+ int (*protect_memory)(unsigned long pfn, unsigned long nr_pages,
+ unsigned int perms);
+
+ /**
+ * @seal_kernel: make the running kernel's text and rodata immutable.
+ * After this call, any attempt to write to kernel text from the
+ * lower plane traps to the secure kernel.
+ */
+ int (*seal_kernel)(void);
+
+ /*
+ * Module authentication
+ *
+ * @validate_module: send a module's ELF blob to the secure kernel
+ * for signature verification. Returns 0 if the signature is valid.
+ *
+ * @set_module_perms: after relocation, set per-section EPT permissions
+ * for the module (text=RX, rodata=R, data=RW).
+ *
+ * @unload_module: notify the secure kernel that a module is being freed
+ * so it can release EPT overrides.
+ */
+ int (*validate_module)(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size);
+ int (*set_module_perms)(const struct module *mod);
+ int (*unload_module)(const struct module *mod);
+
+ /*
+ * Key / certificate management
+ */
+ int (*add_key)(const void *key, size_t key_size, unsigned int flags);
+ int (*revoke_key)(const void *key_id, size_t id_size);
+ int (*send_certs)(const void *certs, size_t certs_size);
+
+ /*
+ * Kexec validation
+ */
+ int (*kexec_validate)(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size);
+ int (*kexec_invalidate)(void);
+};
+
+/* ────────────────────────────────────────────────────────────────────────── */
+/* Core VBS API (called by guest kernel subsystems) */
+/* ────────────────────────────────────────────────────────────────────────── */
+
+#ifdef CONFIG_VBS
+
+/**
+ * vbs_register_backend() - register the platform-specific backend.
+ *
+ * Called once during early boot by the platform detection code.
+ * Only one backend can be active at a time.
+ */
+int vbs_register_backend(const struct vbs_ops *ops);
+
+/**
+ * vbs_available() - returns true if a backend is registered and ready.
+ */
+bool vbs_available(void);
+
+/* Convenience wrappers — each calls through the active backend's ops. */
+int vbs_protect_memory(unsigned long pfn, unsigned long nr_pages,
+ unsigned int perms);
+int vbs_seal_kernel(void);
+int vbs_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size);
+int vbs_set_module_perms(const struct module *mod);
+int vbs_unload_module(const struct module *mod);
+int vbs_add_key(const void *key, size_t key_size, unsigned int flags);
+int vbs_revoke_key(const void *key_id, size_t id_size);
+int vbs_send_certs(const void *certs, size_t certs_size);
+int vbs_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size);
+int vbs_kexec_invalidate(void);
+
+#else /* !CONFIG_VBS */
+
+static inline bool vbs_available(void) { return false; }
+static inline int vbs_protect_memory(unsigned long pfn,
+ unsigned long nr_pages, unsigned int perms) { return -ENOSYS; }
+static inline int vbs_seal_kernel(void) { return -ENOSYS; }
+static inline int vbs_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size) { return -ENOSYS; }
+static inline int vbs_set_module_perms(const struct module *mod)
+ { return -ENOSYS; }
+static inline int vbs_unload_module(const struct module *mod)
+ { return -ENOSYS; }
+static inline int vbs_add_key(const void *key, size_t key_size,
+ unsigned int flags) { return -ENOSYS; }
+static inline int vbs_revoke_key(const void *key_id, size_t id_size)
+ { return -ENOSYS; }
+static inline int vbs_send_certs(const void *certs, size_t certs_size)
+ { return -ENOSYS; }
+static inline int vbs_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size) { return -ENOSYS; }
+static inline int vbs_kexec_invalidate(void) { return -ENOSYS; }
+
+#endif /* CONFIG_VBS */
+#endif /* _LINUX_VBS_H */
diff --git a/security/Kconfig b/security/Kconfig
index f7bf6cdc6229..31ab9b0fa7d0 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -299,6 +299,8 @@ config SECURITY_COMMONCAP_KUNIT_TEST
If unsure, say N.
+source "security/vbs/Kconfig"
+
source "security/Kconfig.hardening"
endmenu
diff --git a/security/Makefile b/security/Makefile
index 4601230ba442..cd85a7615d01 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -27,5 +27,8 @@ obj-$(CONFIG_BPF_LSM) += bpf/
obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
obj-$(CONFIG_SECURITY_IPE) += ipe/
+# Virtualization-Based Security
+obj-$(CONFIG_VBS) += vbs/
+
# Object integrity file lists
obj-$(CONFIG_INTEGRITY) += integrity/
diff --git a/security/vbs/Kconfig b/security/vbs/Kconfig
new file mode 100644
index 000000000000..3d9fb104b1fc
--- /dev/null
+++ b/security/vbs/Kconfig
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VBS
+ bool "Virtualization-Based Security (VBS) support"
+ depends on (X86_64 || ARM64) && VM_PLANES
+ help
+ Enable a transport-agnostic interface between the guest OS
+ (plane-0 / VTL0 / VMPL2+) and the secure kernel (plane-1 /
+ VTL1 / VMPL0 / service TD / RMM).
+
+ The core VBS layer dispatches calls from kernel subsystems
+ (memory protection, module authentication, key management)
+ to a platform-specific backend such as KVM software planes,
+ AMD SEV-SNP SVSM, Intel TDX, Hyper-V VSM, or Arm CCA.
+
+ If unsure, say N.
+
+config VBS_KVM_PLANES
+ bool "VBS backend: KVM software planes"
+ depends on VBS && KVM_GUEST
+ help
+ VBS backend that uses KVM paravirt hypercalls to communicate
+ between plane-0 (normal guest) and plane-1 (secure kernel
+ running in a separate KVM VM plane managed by QEMU).
+
+ Select this if you are running under KVM with VM planes
+ support enabled.
+
+config VBS_SEV_SNP
+ bool "VBS backend: AMD SEV-SNP"
+ depends on VBS && AMD_MEM_ENCRYPT
+ help
+ VBS backend that uses the SVSM (Secure VM Service Module)
+ protocol to communicate with the SVSM running at VMPL0
+ on AMD SEV-SNP platforms.
+
+ Select this if you are running as an SEV-SNP guest with
+ an SVSM providing security services at VMPL0.
+
+config VBS_TDX
+ bool "VBS backend: Intel TDX service TD"
+ depends on VBS && INTEL_TDX_GUEST
+ help
+ VBS backend that uses TDG.VP.VMCALL (TDVMCALL) to communicate
+ with a service TD providing security services on Intel TDX
+ platforms.
+
+ Note: Service TD support is still evolving in the TDX
+ architecture. Select this for development/testing only.
+
+config VBS_HV_VSM
+ bool "VBS backend: Hyper-V VSM"
+ depends on VBS && HYPERV
+ help
+ VBS backend that uses native Hyper-V hypercalls to communicate
+ between VTL0 (normal kernel) and VTL1 (secure kernel).
+
+ Select this if you are running as a Hyper-V guest with
+ Virtual Secure Mode (VSM) enabled.
+
+config VBS_ARM_CCA
+ bool "VBS backend: Arm CCA (Confidential Compute Architecture)"
+ depends on VBS && ARM64
+ help
+ VBS backend that uses the RSI (Realm Services Interface) to
+ communicate between a Realm guest and the RMM (Realm Management
+ Monitor) or a security service on Arm CCA platforms.
+
+ Select this if you are running as a Realm guest under Arm CCA/RME.
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
new file mode 100644
index 000000000000..4f0f26ef4f71
--- /dev/null
+++ b/security/vbs/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_VBS) += vbs.o
+vbs-y := core.o probe.o
+
+obj-$(CONFIG_VBS_KVM_PLANES) += kvm_planes.o
+obj-$(CONFIG_VBS_SEV_SNP) += sev_snp.o
+obj-$(CONFIG_VBS_TDX) += tdx.o
+obj-$(CONFIG_VBS_HV_VSM) += hv_vsm.o
+obj-$(CONFIG_VBS_ARM_CCA) += arm_cca.o
diff --git a/security/vbs/arm_cca.c b/security/vbs/arm_cca.c
new file mode 100644
index 000000000000..21c8b00bb1d1
--- /dev/null
+++ b/security/vbs/arm_cca.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS backend — Arm CCA (Confidential Compute Architecture)
+ *
+ * Uses the RSI (Realm Services Interface) to communicate between the
+ * Realm guest (plane-0) and the RMM (Realm Management Monitor) or a
+ * security service running in a higher-privileged realm.
+ *
+ * Transport: SMC calls via arm_smccc_smc() using SMC_RSI_HOST_CALL for
+ * RPC-style requests to the host/monitor, and direct RSI
+ * commands for memory state management (RIPAS transitions).
+ *
+ * Memory model:
+ * - Protected (RIPAS_RAM): RMM-backed, encrypted, inaccessible to host
+ * - Shared (RIPAS_EMPTY): Host-backed, used for I/O and communication
+ * - The highest IPA bit marks shared vs protected pages
+ */
+
+#include "internal.h"
+
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/arm-smccc.h>
+
+#include <asm/rsi.h>
+
+/* ── VBS host-call command IDs ────────────────────────────────────────── */
+/*
+ * VBS requests are sent to the host via SMC_RSI_HOST_CALL. The host
+ * call structure is placed in a shared (RIPAS_EMPTY) page. The first
+ * 16 bytes encode the VBS-specific command header.
+ */
+#define CCA_VBS_MAGIC 0x56425343 /* "VBSC" */
+
+struct vbs_cca_host_req {
+ __u32 magic; /* CCA_VBS_MAGIC */
+ __u32 call_id; /* enum vbs_call_id / CCA_VBS_* cmd */
+ __u32 arg_size; /* bytes of payload following this hdr */
+ __u32 reserved;
+ __u8 payload[];
+} __packed;
+
+struct vbs_cca_host_resp {
+ __s32 status; /* 0 = success, negative errno */
+ __u32 resp_size;
+ __u8 payload[];
+} __packed;
+
+/* VBS sub-commands */
+#define CCA_VBS_INIT 0
+#define CCA_VBS_SHUTDOWN 1
+#define CCA_VBS_PROTECT_MEMORY 2
+#define CCA_VBS_SEAL_KERNEL 3
+#define CCA_VBS_VALIDATE_MODULE 4
+#define CCA_VBS_SET_MODULE_PERMS 5
+#define CCA_VBS_UNLOAD_MODULE 6
+#define CCA_VBS_ADD_KEY 7
+#define CCA_VBS_REVOKE_KEY 8
+#define CCA_VBS_SEND_CERTS 9
+#define CCA_VBS_KEXEC_VALIDATE 10
+#define CCA_VBS_KEXEC_INVALIDATE 11
+
+/* Shared pages for host communication (RIPAS_EMPTY / decrypted) */
+static void *cca_req_page;
+static void *cca_resp_page;
+
+/* ── low-level host call ──────────────────────────────────────────────── */
+
+static int cca_vbs_host_call(u32 cmd, const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ struct vbs_cca_host_req *req;
+ struct vbs_cca_host_resp *rsp;
+ struct arm_smccc_res res;
+ unsigned long ret;
+
+ if (!cca_req_page || !cca_resp_page)
+ return -ENOMEM;
+
+ if (arg_size > PAGE_SIZE - sizeof(*req))
+ return -E2BIG;
+
+ /* Build request in the shared page */
+ req = cca_req_page;
+ memset(req, 0, PAGE_SIZE);
+ req->magic = CCA_VBS_MAGIC;
+ req->call_id = cmd;
+ req->arg_size = arg_size;
+ if (arg_size && arg)
+ memcpy(req->payload, arg, arg_size);
+
+ memset(cca_resp_page, 0, PAGE_SIZE);
+
+ /*
+ * SMC_RSI_HOST_CALL: arg1 = IPA of host call structure.
+ * The host (VMM) reads the request, processes it, writes the
+ * response into cca_resp_page, then returns control.
+ */
+ arm_smccc_smc(SMC_RSI_HOST_CALL, virt_to_phys(cca_req_page),
+ 0, 0, 0, 0, 0, 0, &res);
+ ret = res.a0;
+ if (ret != RSI_SUCCESS) {
+ pr_err_ratelimited("vbs-cca: RSI host call failed (%lu)\n",
+ ret);
+ return -EIO;
+ }
+
+ /* Read response */
+ rsp = cca_resp_page;
+ if (rsp->status)
+ return rsp->status;
+
+ if (resp && resp_size) {
+ size_t copy = min_t(size_t, resp_size, rsp->resp_size);
+
+ memcpy(resp, rsp->payload, copy);
+ }
+ return 0;
+}
+
+static int cca_vbs_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ return cca_vbs_host_call(id, arg, arg_size, resp, resp_size);
+}
+
+/* ── memory protection ────────────────────────────────────────────────── */
+
+/*
+ * On Arm CCA, memory protection is handled natively via RIPAS transitions.
+ * The RMM enforces that protected (RIPAS_RAM) pages are inaccessible to
+ * the host. For VBS-style per-page permission control (R/W/X), we
+ * forward the request to the security service via a host call.
+ */
+
+struct vbs_cca_protect_args {
+ __u64 pfn;
+ __u64 nr_pages;
+ __u32 perms;
+} __packed;
+
+static int cca_vbs_protect_memory(unsigned long pfn, unsigned long nr_pages,
+ unsigned int perms)
+{
+ struct vbs_cca_protect_args args = {
+ .pfn = pfn,
+ .nr_pages = nr_pages,
+ .perms = perms,
+ };
+
+ return cca_vbs_host_call(CCA_VBS_PROTECT_MEMORY,
+ &args, sizeof(args), NULL, 0);
+}
+
+static int cca_vbs_seal_kernel(void)
+{
+ return cca_vbs_host_call(CCA_VBS_SEAL_KERNEL, NULL, 0, NULL, 0);
+}
+
+/* ── module authentication ────────────────────────────────────────────── */
+
+static int cca_vbs_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size)
+{
+ return cca_vbs_host_call(CCA_VBS_VALIDATE_MODULE, NULL, 0, NULL, 0);
+}
+
+static int cca_vbs_set_module_perms(const struct module *mod)
+{
+ return cca_vbs_host_call(CCA_VBS_SET_MODULE_PERMS, NULL, 0, NULL, 0);
+}
+
+static int cca_vbs_unload_module(const struct module *mod)
+{
+ return cca_vbs_host_call(CCA_VBS_UNLOAD_MODULE, NULL, 0, NULL, 0);
+}
+
+/* ── key / certificate management ─────────────────────────────────────── */
+
+static int cca_vbs_add_key(const void *key, size_t key_size,
+ unsigned int flags)
+{
+ return cca_vbs_host_call(CCA_VBS_ADD_KEY, key, key_size, NULL, 0);
+}
+
+static int cca_vbs_revoke_key(const void *key_id, size_t id_size)
+{
+ return cca_vbs_host_call(CCA_VBS_REVOKE_KEY, key_id, id_size, NULL, 0);
+}
+
+static int cca_vbs_send_certs(const void *certs, size_t certs_size)
+{
+ return cca_vbs_host_call(CCA_VBS_SEND_CERTS,
+ certs, certs_size, NULL, 0);
+}
+
+/* ── kexec validation ─────────────────────────────────────────────────── */
+
+static int cca_vbs_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size)
+{
+ return cca_vbs_host_call(CCA_VBS_KEXEC_VALIDATE, NULL, 0, NULL, 0);
+}
+
+static int cca_vbs_kexec_invalidate(void)
+{
+ return cca_vbs_host_call(CCA_VBS_KEXEC_INVALIDATE, NULL, 0, NULL, 0);
+}
+
+/* ── lifecycle ────────────────────────────────────────────────────────── */
+
+static int cca_vbs_init(void)
+{
+ int ret;
+
+ /*
+ * Allocate shared pages for host communication. Convert them
+ * to RIPAS_EMPTY so the host/VMM can access them.
+ */
+ cca_req_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ cca_resp_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (!cca_req_page || !cca_resp_page) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ /* Mark as shared (RIPAS_EMPTY) for host access */
+ ret = set_memory_decrypted((unsigned long)cca_req_page, 1);
+ if (ret)
+ goto fail;
+ ret = set_memory_decrypted((unsigned long)cca_resp_page, 1);
+ if (ret)
+ goto fail_re_encrypt_req;
+
+ ret = cca_vbs_host_call(CCA_VBS_INIT, NULL, 0, NULL, 0);
+ if (ret) {
+ pr_err("vbs-cca: realm VBS init failed (%d)\n", ret);
+ goto fail_re_encrypt;
+ }
+
+ pr_info("vbs-cca: connected to Arm CCA security service\n");
+ return 0;
+
+fail_re_encrypt:
+ set_memory_encrypted((unsigned long)cca_resp_page, 1);
+fail_re_encrypt_req:
+ set_memory_encrypted((unsigned long)cca_req_page, 1);
+fail:
+ free_page((unsigned long)cca_req_page);
+ free_page((unsigned long)cca_resp_page);
+ cca_req_page = cca_resp_page = NULL;
+ return ret;
+}
+
+static void cca_vbs_shutdown(void)
+{
+ cca_vbs_host_call(CCA_VBS_SHUTDOWN, NULL, 0, NULL, 0);
+
+ if (cca_resp_page) {
+ set_memory_encrypted((unsigned long)cca_resp_page, 1);
+ free_page((unsigned long)cca_resp_page);
+ }
+ if (cca_req_page) {
+ set_memory_encrypted((unsigned long)cca_req_page, 1);
+ free_page((unsigned long)cca_req_page);
+ }
+ cca_req_page = cca_resp_page = NULL;
+}
+
+/* ── ops table & registration ─────────────────────────────────────────── */
+
+static const struct vbs_ops cca_vbs_ops = {
+ .name = "arm-cca",
+ .init = cca_vbs_init,
+ .shutdown = cca_vbs_shutdown,
+ .vtl_call = cca_vbs_vtl_call,
+ .protect_memory = cca_vbs_protect_memory,
+ .seal_kernel = cca_vbs_seal_kernel,
+ .validate_module = cca_vbs_validate_module,
+ .set_module_perms = cca_vbs_set_module_perms,
+ .unload_module = cca_vbs_unload_module,
+ .add_key = cca_vbs_add_key,
+ .revoke_key = cca_vbs_revoke_key,
+ .send_certs = cca_vbs_send_certs,
+ .kexec_validate = cca_vbs_kexec_validate,
+ .kexec_invalidate = cca_vbs_kexec_invalidate,
+};
+
+/* ── detection & probe (called from probe.c) ──────────────────────────── */
+
+bool __init vbs_cca_detect(void)
+{
+ return is_realm_world();
+}
+
+const struct vbs_ops *vbs_cca_get_ops(void)
+{
+ return &cca_vbs_ops;
+}
diff --git a/security/vbs/core.c b/security/vbs/core.c
new file mode 100644
index 000000000000..352590d88136
--- /dev/null
+++ b/security/vbs/core.c
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS — Virtualization-Based Security core
+ *
+ * Dispatches calls from guest kernel subsystems to the active
+ * platform-specific backend (KVM planes, SVSM, Hyper-V VSM, …).
+ */
+
+#include "internal.h"
+
+#include <linux/mutex.h>
+
+static const struct vbs_ops *vbs_backend;
+static DEFINE_MUTEX(vbs_lock);
+
+int vbs_register_backend(const struct vbs_ops *ops)
+{
+ int ret = 0;
+
+ if (!ops || !ops->name)
+ return -EINVAL;
+
+ mutex_lock(&vbs_lock);
+ if (vbs_backend) {
+ pr_err("vbs: backend \"%s\" already registered, rejecting \"%s\"\n",
+ vbs_backend->name, ops->name);
+ ret = -EBUSY;
+ } else {
+ vbs_backend = ops;
+ pr_info("vbs: registered backend \"%s\"\n", ops->name);
+ }
+ mutex_unlock(&vbs_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(vbs_register_backend);
+
+bool vbs_available(void)
+{
+ return READ_ONCE(vbs_backend) != NULL;
+}
+EXPORT_SYMBOL_GPL(vbs_available);
+
+/* ── convenience wrappers ─────────────────────────────────────────────── */
+
+int vbs_protect_memory(unsigned long pfn, unsigned long nr_pages,
+ unsigned int perms)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->protect_memory)
+ return -EOPNOTSUPP;
+ return ops->protect_memory(pfn, nr_pages, perms);
+}
+EXPORT_SYMBOL_GPL(vbs_protect_memory);
+
+int vbs_seal_kernel(void)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->seal_kernel)
+ return -EOPNOTSUPP;
+ return ops->seal_kernel();
+}
+EXPORT_SYMBOL_GPL(vbs_seal_kernel);
+
+int vbs_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->validate_module)
+ return -EOPNOTSUPP;
+ return ops->validate_module(elf, elf_size, sig, sig_size);
+}
+EXPORT_SYMBOL_GPL(vbs_validate_module);
+
+int vbs_set_module_perms(const struct module *mod)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->set_module_perms)
+ return -EOPNOTSUPP;
+ return ops->set_module_perms(mod);
+}
+EXPORT_SYMBOL_GPL(vbs_set_module_perms);
+
+int vbs_unload_module(const struct module *mod)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->unload_module)
+ return -EOPNOTSUPP;
+ return ops->unload_module(mod);
+}
+EXPORT_SYMBOL_GPL(vbs_unload_module);
+
+int vbs_add_key(const void *key, size_t key_size, unsigned int flags)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->add_key)
+ return -EOPNOTSUPP;
+ return ops->add_key(key, key_size, flags);
+}
+EXPORT_SYMBOL_GPL(vbs_add_key);
+
+int vbs_revoke_key(const void *key_id, size_t id_size)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->revoke_key)
+ return -EOPNOTSUPP;
+ return ops->revoke_key(key_id, id_size);
+}
+EXPORT_SYMBOL_GPL(vbs_revoke_key);
+
+int vbs_send_certs(const void *certs, size_t certs_size)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->send_certs)
+ return -EOPNOTSUPP;
+ return ops->send_certs(certs, certs_size);
+}
+EXPORT_SYMBOL_GPL(vbs_send_certs);
+
+int vbs_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->kexec_validate)
+ return -EOPNOTSUPP;
+ return ops->kexec_validate(kernel, kernel_size, sig, sig_size);
+}
+EXPORT_SYMBOL_GPL(vbs_kexec_validate);
+
+int vbs_kexec_invalidate(void)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->kexec_invalidate)
+ return -EOPNOTSUPP;
+ return ops->kexec_invalidate();
+}
+EXPORT_SYMBOL_GPL(vbs_kexec_invalidate);
diff --git a/security/vbs/hv_vsm.c b/security/vbs/hv_vsm.c
new file mode 100644
index 000000000000..981ec7fa95e3
--- /dev/null
+++ b/security/vbs/hv_vsm.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS backend — Hyper-V VSM (Virtual Secure Mode)
+ *
+ * Uses native Hyper-V hypercalls to communicate between VTL0 (normal
+ * kernel) and VTL1 (secure kernel / SKCI).
+ *
+ * Transport: hv_do_hypercall() with VTL-targeted input pages.
+ *
+ * On Hyper-V the VTL architecture is a first-class feature:
+ * - VTL0 runs the normal OS kernel.
+ * - VTL1 runs the secure kernel that enforces HVCI / Credential Guard.
+ * - VTL switches are performed via HvVtlCall / HvVtlReturn hypercalls.
+ * - Memory protection is enforced per-VTL via the second-level address
+ * translation (SLAT / EPT / NPT) controlled by the hypervisor.
+ */
+
+#include "internal.h"
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+#include <asm/mshyperv.h>
+#include <asm/hyperv-tlfs.h>
+
+/* ── Hyper-V VTL call / return hypercall numbers ──────────────────────── */
+#define HVCALL_VTL_CALL 0x0011
+#define HVCALL_VTL_RETURN 0x0012
+
+/*
+ * VBS-specific hypercall — used to send structured VBS requests to VTL1.
+ * This sits in the vendor-extension range and is routed by the Hyper-V
+ * secure kernel to the appropriate VBS service handler.
+ */
+#define HVCALL_VBS_REQUEST 0x0200
+
+/* ── shared-memory request / response layout ──────────────────────────── */
+
+struct vbs_hv_request {
+ __u32 call_id; /* enum vbs_call_id */
+ __u32 arg_size; /* bytes of payload following this hdr */
+ __u8 payload[];
+} __packed;
+
+struct vbs_hv_response {
+ __s32 status; /* 0 = success, negative errno */
+ __u32 resp_size;
+ __u8 payload[];
+} __packed;
+
+/* Hypercall input / output pages (one page each, allocated once) */
+static void *hv_input_page;
+static void *hv_output_page;
+
+/* ── low-level VTL call ───────────────────────────────────────────────── */
+
+static int hv_vsm_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ struct vbs_hv_request *req;
+ struct vbs_hv_response *rsp;
+ u64 status;
+
+ if (!hv_input_page || !hv_output_page)
+ return -ENOMEM;
+
+ if (arg_size > PAGE_SIZE - sizeof(*req))
+ return -E2BIG;
+
+ /* Build request in the hypercall input page */
+ req = hv_input_page;
+ memset(req, 0, PAGE_SIZE);
+ req->call_id = id;
+ req->arg_size = arg_size;
+ if (arg_size && arg)
+ memcpy(req->payload, arg, arg_size);
+
+ memset(hv_output_page, 0, PAGE_SIZE);
+
+ status = hv_do_hypercall(HVCALL_VBS_REQUEST,
+ hv_input_page, hv_output_page);
+ if (!hv_result_success(status)) {
+ pr_err_ratelimited("vbs-hv: hypercall failed (0x%llx)\n",
+ status);
+ return -EIO;
+ }
+
+ /* Read response from the output page */
+ rsp = hv_output_page;
+ if (rsp->status)
+ return rsp->status;
+
+ if (resp && resp_size) {
+ size_t copy = min_t(size_t, resp_size, rsp->resp_size);
+
+ memcpy(resp, rsp->payload, copy);
+ }
+ return 0;
+}
+
+/* ── memory protection ────────────────────────────────────────────────── */
+
+struct vbs_hv_protect_args {
+ __u64 pfn;
+ __u64 nr_pages;
+ __u32 perms;
+} __packed;
+
+static int hv_vsm_protect_memory(unsigned long pfn, unsigned long nr_pages,
+ unsigned int perms)
+{
+ struct vbs_hv_protect_args args = {
+ .pfn = pfn,
+ .nr_pages = nr_pages,
+ .perms = perms,
+ };
+
+ return hv_vsm_vtl_call(VBS_CALL_PROTECT_MEMORY,
+ &args, sizeof(args), NULL, 0);
+}
+
+static int hv_vsm_seal_kernel(void)
+{
+ return hv_vsm_vtl_call(VBS_CALL_SEAL_KERNEL, NULL, 0, NULL, 0);
+}
+
+/* ── module authentication ────────────────────────────────────────────── */
+
+static int hv_vsm_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size)
+{
+ return hv_vsm_vtl_call(VBS_CALL_VALIDATE_MODULE, NULL, 0, NULL, 0);
+}
+
+static int hv_vsm_set_module_perms(const struct module *mod)
+{
+ return hv_vsm_vtl_call(VBS_CALL_SET_MODULE_PERMS, NULL, 0, NULL, 0);
+}
+
+static int hv_vsm_unload_module(const struct module *mod)
+{
+ return hv_vsm_vtl_call(VBS_CALL_UNLOAD_MODULE, NULL, 0, NULL, 0);
+}
+
+/* ── key / certificate management ─────────────────────────────────────── */
+
+static int hv_vsm_add_key(const void *key, size_t key_size,
+ unsigned int flags)
+{
+ return hv_vsm_vtl_call(VBS_CALL_ADD_KEY, key, key_size, NULL, 0);
+}
+
+static int hv_vsm_revoke_key(const void *key_id, size_t id_size)
+{
+ return hv_vsm_vtl_call(VBS_CALL_REVOKE_KEY, key_id, id_size, NULL, 0);
+}
+
+static int hv_vsm_send_certs(const void *certs, size_t certs_size)
+{
+ return hv_vsm_vtl_call(VBS_CALL_SEND_CERTS,
+ certs, certs_size, NULL, 0);
+}
+
+/* ── kexec validation ─────────────────────────────────────────────────── */
+
+static int hv_vsm_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size)
+{
+ return hv_vsm_vtl_call(VBS_CALL_KEXEC_VALIDATE, NULL, 0, NULL, 0);
+}
+
+static int hv_vsm_kexec_invalidate(void)
+{
+ return hv_vsm_vtl_call(VBS_CALL_KEXEC_INVALIDATE, NULL, 0, NULL, 0);
+}
+
+/* ── lifecycle ────────────────────────────────────────────────────────── */
+
+static int hv_vsm_init(void)
+{
+ int ret;
+
+ /*
+ * Use the Hyper-V provided hypercall input/output pages.
+ * Allocate our own pair so we don't conflict with other users.
+ */
+ hv_input_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ hv_output_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (!hv_input_page || !hv_output_page) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = hv_vsm_vtl_call(VBS_CALL_INIT, NULL, 0, NULL, 0);
+ if (ret) {
+ pr_err("vbs-hv: VTL1 secure kernel INIT failed (%d)\n", ret);
+ goto fail;
+ }
+
+ pr_info("vbs-hv: connected to Hyper-V VTL1 secure kernel\n");
+ return 0;
+
+fail:
+ free_page((unsigned long)hv_input_page);
+ free_page((unsigned long)hv_output_page);
+ hv_input_page = hv_output_page = NULL;
+ return ret;
+}
+
+static void hv_vsm_shutdown(void)
+{
+ hv_vsm_vtl_call(VBS_CALL_SHUTDOWN, NULL, 0, NULL, 0);
+ free_page((unsigned long)hv_input_page);
+ free_page((unsigned long)hv_output_page);
+ hv_input_page = hv_output_page = NULL;
+}
+
+/* ── ops table & registration ─────────────────────────────────────────── */
+
+static const struct vbs_ops hv_vsm_ops = {
+ .name = "hv-vsm",
+ .init = hv_vsm_init,
+ .shutdown = hv_vsm_shutdown,
+ .vtl_call = hv_vsm_vtl_call,
+ .protect_memory = hv_vsm_protect_memory,
+ .seal_kernel = hv_vsm_seal_kernel,
+ .validate_module = hv_vsm_validate_module,
+ .set_module_perms = hv_vsm_set_module_perms,
+ .unload_module = hv_vsm_unload_module,
+ .add_key = hv_vsm_add_key,
+ .revoke_key = hv_vsm_revoke_key,
+ .send_certs = hv_vsm_send_certs,
+ .kexec_validate = hv_vsm_kexec_validate,
+ .kexec_invalidate = hv_vsm_kexec_invalidate,
+};
+
+/* ── detection & probe (called from probe.c) ──────────────────────────── */
+
+bool __init vbs_hv_vsm_detect(void)
+{
+ if (!hv_is_hyperv_initialized())
+ return false;
+
+ if (ms_hyperv.vtl != 0) {
+ pr_debug("vbs-hv: not at VTL0 (vtl=%u), skipping\n",
+ ms_hyperv.vtl);
+ return false;
+ }
+
+ return true;
+}
+
+const struct vbs_ops *vbs_hv_vsm_get_ops(void)
+{
+ return &hv_vsm_ops;
+}
diff --git a/security/vbs/internal.h b/security/vbs/internal.h
new file mode 100644
index 000000000000..415621c993b8
--- /dev/null
+++ b/security/vbs/internal.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * VBS internal header — shared between probe.c and backend implementations.
+ */
+#ifndef _SECURITY_VBS_INTERNAL_H
+#define _SECURITY_VBS_INTERNAL_H
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/printk.h>
+#include <linux/vbs.h>
+
+/* Each backend exports a detect + get_ops pair for the centralized probe. */
+
+#ifdef CONFIG_VBS_SEV_SNP
+bool __init vbs_sev_snp_detect(void);
+const struct vbs_ops *vbs_sev_snp_get_ops(void);
+#endif
+
+#ifdef CONFIG_VBS_TDX
+bool __init vbs_tdx_detect(void);
+const struct vbs_ops *vbs_tdx_get_ops(void);
+#endif
+
+#ifdef CONFIG_VBS_ARM_CCA
+bool __init vbs_cca_detect(void);
+const struct vbs_ops *vbs_cca_get_ops(void);
+#endif
+
+#ifdef CONFIG_VBS_HV_VSM
+bool __init vbs_hv_vsm_detect(void);
+const struct vbs_ops *vbs_hv_vsm_get_ops(void);
+#endif
+
+#ifdef CONFIG_VBS_KVM_PLANES
+bool __init vbs_kvm_planes_detect(void);
+const struct vbs_ops *vbs_kvm_planes_get_ops(void);
+#endif
+
+#endif /* _SECURITY_VBS_INTERNAL_H */
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
new file mode 100644
index 000000000000..3526f7c429c3
--- /dev/null
+++ b/security/vbs/kvm_planes.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS backend — KVM software planes
+ *
+ * Uses KVM paravirt hypercalls to communicate between plane-0 (normal
+ * guest kernel) and plane-1 (secure kernel running in a separate KVM
+ * plane managed by QEMU).
+ *
+ * Transport: kvm_hypercall{0..4}() → KVM_EXIT_HYPERCALL → QEMU → plane-1
+ *
+ * The shared-memory VTL-call protocol works as follows:
+ * 1. Plane-0 fills a request buffer in shared memory.
+ * 2. Plane-0 issues a KVM hypercall carrying the physical address
+ * and size of the request.
+ * 3. QEMU (or the host) delivers the request to the plane-1 vCPU.
+ * 4. Plane-1 processes the request and writes a response.
+ * 5. Plane-0 reads the response from shared memory.
+ */
+
+#include "internal.h"
+
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <asm/kvm_para.h>
+
+/* ── hypercall numbers for VBS VTL calls (plane-0 → plane-1) ──────────── */
+/*
+ * These extend the existing KVM_HC_* numbering. The host (KVM + QEMU)
+ * intercepts them and routes them to the secure-kernel plane.
+ */
+#define KVM_HC_VBS_VTL_CALL 15
+
+/* ── shared-memory request / response layout ──────────────────────────── */
+
+struct vbs_kvm_request {
+ __u32 call_id; /* enum vbs_call_id */
+ __u32 arg_size; /* bytes of payload following this hdr */
+ __u8 payload[]; /* variable-length argument data */
+} __packed;
+
+struct vbs_kvm_response {
+ __s32 status; /* 0 = success, negative errno */
+ __u32 resp_size; /* bytes of payload following this hdr */
+ __u8 payload[]; /* variable-length response data */
+} __packed;
+
+/*
+ * A single page is used for each direction. That gives ~4 KiB of
+ * payload per call, which is enough for all current VBS operations.
+ */
+static void *kvm_req_page; /* request (plane-0 writes, plane-1 reads) */
+static void *kvm_resp_page; /* response (plane-1 writes, plane-0 reads) */
+
+/* ── low-level VTL call ───────────────────────────────────────────────── */
+
+static int kvm_planes_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ struct vbs_kvm_request *req;
+ struct vbs_kvm_response *rsp;
+ long hc_ret;
+
+ if (!kvm_req_page || !kvm_resp_page)
+ return -ENOMEM;
+
+ if (arg_size > PAGE_SIZE - sizeof(*req))
+ return -E2BIG;
+
+ /* Build request in the shared page */
+ req = kvm_req_page;
+ req->call_id = id;
+ req->arg_size = arg_size;
+ if (arg_size && arg)
+ memcpy(req->payload, arg, arg_size);
+
+ /* Issue hypercall: pass physical addresses of req & resp pages */
+ hc_ret = kvm_hypercall2(KVM_HC_VBS_VTL_CALL,
+ virt_to_phys(kvm_req_page),
+ virt_to_phys(kvm_resp_page));
+ if (hc_ret) {
+ pr_err_ratelimited("vbs-kvm: hypercall failed (%ld)\n", hc_ret);
+ return -EIO;
+ }
+
+ /* Read response */
+ rsp = kvm_resp_page;
+ if (rsp->status)
+ return rsp->status;
+
+ if (resp && resp_size) {
+ size_t copy = min_t(size_t, resp_size, rsp->resp_size);
+
+ memcpy(resp, rsp->payload, copy);
+ }
+ return 0;
+}
+
+/* ── memory protection ────────────────────────────────────────────────── */
+
+struct vbs_protect_args {
+ __u64 pfn;
+ __u64 nr_pages;
+ __u32 perms;
+} __packed;
+
+static int kvm_planes_protect_memory(unsigned long pfn,
+ unsigned long nr_pages,
+ unsigned int perms)
+{
+ struct vbs_protect_args args = {
+ .pfn = pfn,
+ .nr_pages = nr_pages,
+ .perms = perms,
+ };
+
+ return kvm_planes_vtl_call(VBS_CALL_PROTECT_MEMORY,
+ &args, sizeof(args), NULL, 0);
+}
+
+static int kvm_planes_seal_kernel(void)
+{
+ return kvm_planes_vtl_call(VBS_CALL_SEAL_KERNEL, NULL, 0, NULL, 0);
+}
+
+/* ── module authentication ────────────────────────────────────────────── */
+
+static int kvm_planes_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size)
+{
+ /*
+ * Module blobs can be large — for the KVM planes backend we pass
+ * the physical address and size to plane-1 via the VTL call and
+ * let plane-1 map/read the pages directly from its EPT view.
+ * For now, a stub that signals "not yet implemented".
+ */
+ return kvm_planes_vtl_call(VBS_CALL_VALIDATE_MODULE,
+ NULL, 0, NULL, 0);
+}
+
+static int kvm_planes_set_module_perms(const struct module *mod)
+{
+ return kvm_planes_vtl_call(VBS_CALL_SET_MODULE_PERMS,
+ NULL, 0, NULL, 0);
+}
+
+static int kvm_planes_unload_module(const struct module *mod)
+{
+ return kvm_planes_vtl_call(VBS_CALL_UNLOAD_MODULE,
+ NULL, 0, NULL, 0);
+}
+
+/* ── key / certificate management ─────────────────────────────────────── */
+
+static int kvm_planes_add_key(const void *key, size_t key_size,
+ unsigned int flags)
+{
+ return kvm_planes_vtl_call(VBS_CALL_ADD_KEY, key, key_size, NULL, 0);
+}
+
+static int kvm_planes_revoke_key(const void *key_id, size_t id_size)
+{
+ return kvm_planes_vtl_call(VBS_CALL_REVOKE_KEY,
+ key_id, id_size, NULL, 0);
+}
+
+static int kvm_planes_send_certs(const void *certs, size_t certs_size)
+{
+ return kvm_planes_vtl_call(VBS_CALL_SEND_CERTS,
+ certs, certs_size, NULL, 0);
+}
+
+/* ── kexec validation ─────────────────────────────────────────────────── */
+
+static int kvm_planes_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size)
+{
+ return kvm_planes_vtl_call(VBS_CALL_KEXEC_VALIDATE,
+ NULL, 0, NULL, 0);
+}
+
+static int kvm_planes_kexec_invalidate(void)
+{
+ return kvm_planes_vtl_call(VBS_CALL_KEXEC_INVALIDATE,
+ NULL, 0, NULL, 0);
+}
+
+/* ── lifecycle ────────────────────────────────────────────────────────── */
+
+static int kvm_planes_init(void)
+{
+ int ret;
+
+ kvm_req_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ kvm_resp_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (!kvm_req_page || !kvm_resp_page) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ ret = kvm_planes_vtl_call(VBS_CALL_INIT, NULL, 0, NULL, 0);
+ if (ret) {
+ pr_err("vbs-kvm: plane-1 INIT call failed (%d)\n", ret);
+ goto fail;
+ }
+
+ pr_info("vbs-kvm: connected to plane-1 secure kernel\n");
+ return 0;
+fail:
+ free_page((unsigned long)kvm_req_page);
+ free_page((unsigned long)kvm_resp_page);
+ kvm_req_page = kvm_resp_page = NULL;
+ return ret;
+}
+
+static void kvm_planes_shutdown(void)
+{
+ kvm_planes_vtl_call(VBS_CALL_SHUTDOWN, NULL, 0, NULL, 0);
+ free_page((unsigned long)kvm_req_page);
+ free_page((unsigned long)kvm_resp_page);
+ kvm_req_page = kvm_resp_page = NULL;
+}
+
+/* ── ops table & registration ─────────────────────────────────────────── */
+
+static const struct vbs_ops kvm_planes_ops = {
+ .name = "kvm-planes",
+ .init = kvm_planes_init,
+ .shutdown = kvm_planes_shutdown,
+ .vtl_call = kvm_planes_vtl_call,
+ .protect_memory = kvm_planes_protect_memory,
+ .seal_kernel = kvm_planes_seal_kernel,
+ .validate_module = kvm_planes_validate_module,
+ .set_module_perms = kvm_planes_set_module_perms,
+ .unload_module = kvm_planes_unload_module,
+ .add_key = kvm_planes_add_key,
+ .revoke_key = kvm_planes_revoke_key,
+ .send_certs = kvm_planes_send_certs,
+ .kexec_validate = kvm_planes_kexec_validate,
+ .kexec_invalidate = kvm_planes_kexec_invalidate,
+};
+
+/* ── detection & probe (called from probe.c) ──────────────────────────── */
+
+bool __init vbs_kvm_planes_detect(void)
+{
+ if (!kvm_para_available()) {
+ pr_debug("vbs-kvm: KVM paravirt not available\n");
+ return false;
+ }
+ return true;
+}
+
+const struct vbs_ops *vbs_kvm_planes_get_ops(void)
+{
+ return &kvm_planes_ops;
+}
diff --git a/security/vbs/probe.c b/security/vbs/probe.c
new file mode 100644
index 000000000000..292f3663a996
--- /dev/null
+++ b/security/vbs/probe.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS platform detection and backend selection
+ *
+ * Single initcall that probes the platform and registers the appropriate
+ * VBS backend. Detection order (first match wins):
+ *
+ * 1. Hardware CoCo — these are mutually exclusive by nature:
+ * a. AMD SEV-SNP with SVSM (VMPL > 0, SVSM at VMPL0)
+ * b. Intel TDX service TD (running inside a Trust Domain)
+ * c. Arm CCA (Realm guest with RSI)
+ *
+ * 2. Hypervisor-specific:
+ * d. Hyper-V VSM (Hyper-V guest at VTL0)
+ *
+ * 3. Software emulation:
+ * e. KVM software planes (KVM paravirt guest)
+ *
+ * Only one backend can be active. The first successful probe wins.
+ */
+
+#include "internal.h"
+
+/* Stubs for backends not configured */
+#ifndef CONFIG_VBS_SEV_SNP
+static inline bool vbs_sev_snp_detect(void) { return false; }
+static inline const struct vbs_ops *vbs_sev_snp_get_ops(void) { return NULL; }
+#endif
+#ifndef CONFIG_VBS_TDX
+static inline bool vbs_tdx_detect(void) { return false; }
+static inline const struct vbs_ops *vbs_tdx_get_ops(void) { return NULL; }
+#endif
+#ifndef CONFIG_VBS_ARM_CCA
+static inline bool vbs_cca_detect(void) { return false; }
+static inline const struct vbs_ops *vbs_cca_get_ops(void) { return NULL; }
+#endif
+#ifndef CONFIG_VBS_HV_VSM
+static inline bool vbs_hv_vsm_detect(void) { return false; }
+static inline const struct vbs_ops *vbs_hv_vsm_get_ops(void) { return NULL; }
+#endif
+#ifndef CONFIG_VBS_KVM_PLANES
+static inline bool vbs_kvm_planes_detect(void) { return false; }
+static inline const struct vbs_ops *vbs_kvm_planes_get_ops(void) { return NULL; }
+#endif
+
+/* ── probe table ──────────────────────────────────────────────────────── */
+
+struct vbs_probe_entry {
+ const char *name;
+ bool (*detect)(void);
+ const struct vbs_ops *(*get_ops)(void);
+};
+
+static const struct vbs_probe_entry vbs_probe_table[] __initconst = {
+ /*
+ * Hardware confidential-compute backends first.
+ * These are mutually exclusive — a machine is SEV-SNP *or* TDX
+ * *or* CCA, never more than one.
+ */
+ { "AMD SEV-SNP", vbs_sev_snp_detect, vbs_sev_snp_get_ops },
+ { "Intel TDX", vbs_tdx_detect, vbs_tdx_get_ops },
+ { "Arm CCA", vbs_cca_detect, vbs_cca_get_ops },
+
+ /* Hypervisor-specific */
+ { "Hyper-V VSM", vbs_hv_vsm_detect, vbs_hv_vsm_get_ops },
+
+ /* Software emulation (lowest priority) */
+ { "KVM planes", vbs_kvm_planes_detect, vbs_kvm_planes_get_ops },
+};
+
+/* ── single boot-time probe ───────────────────────────────────────────── */
+
+static int __init vbs_probe_init(void)
+{
+ int i, ret;
+
+ for (i = 0; i < ARRAY_SIZE(vbs_probe_table); i++) {
+ const struct vbs_probe_entry *e = &vbs_probe_table[i];
+
+ if (!e->detect())
+ continue;
+
+ pr_info("vbs: detected %s platform\n", e->name);
+
+ ret = vbs_register_backend(e->get_ops());
+ if (ret) {
+ pr_err("vbs: failed to register %s backend (%d)\n",
+ e->name, ret);
+ return ret;
+ }
+ return 0;
+ }
+
+ pr_debug("vbs: no supported platform detected\n");
+ return 0;
+}
+
+/*
+ * Run at device_initcall level: platform detection (CPUID, MSRs, SMCCC)
+ * is complete by this point, but subsystems that consume VBS (module
+ * loading, HEKI) have not yet started.
+ */
+device_initcall(vbs_probe_init);
diff --git a/security/vbs/sev_snp.c b/security/vbs/sev_snp.c
new file mode 100644
index 000000000000..510a2245a0e7
--- /dev/null
+++ b/security/vbs/sev_snp.c
@@ -0,0 +1,224 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS backend — AMD SEV-SNP
+ *
+ * Uses the SVSM (Secure VM Service Module) protocol to communicate
+ * between the guest (VMPL2+) and the SVSM running at VMPL0.
+ *
+ * Transport: VMGEXIT with SVM_VMGEXIT_SNP_RUN_VMPL exit code,
+ * parameters passed via the SVSM Calling Area (CAA).
+ *
+ * The SVSM already provides core services (PVALIDATE, attestation,
+ * vTPM). This backend extends it with VBS-specific calls for
+ * memory protection, module authentication, and key management
+ * using a new VBS SVSM protocol number.
+ */
+
+#include "internal.h"
+
+#include <linux/cc_platform.h>
+
+#include <asm/sev.h>
+
+/*
+ * VBS SEV-SNP protocol — extends the existing SVSM protocol numbering.
+ * Protocol 0 = core, 1 = attestation, 2 = vTPM, 3 = VBS.
+ */
+#define SEV_SNP_VBS_CALL(x) ((3ULL << 32) | (x))
+
+/* VBS-specific SEV-SNP call IDs (mapped from enum vbs_call_id) */
+#define SEV_SNP_VBS_INIT 0
+#define SEV_SNP_VBS_SHUTDOWN 1
+#define SEV_SNP_VBS_PROTECT_MEMORY 2
+#define SEV_SNP_VBS_SEAL_KERNEL 3
+#define SEV_SNP_VBS_VALIDATE_MODULE 4
+#define SEV_SNP_VBS_SET_MODULE_PERMS 5
+#define SEV_SNP_VBS_UNLOAD_MODULE 6
+#define SEV_SNP_VBS_ADD_KEY 7
+#define SEV_SNP_VBS_REVOKE_KEY 8
+#define SEV_SNP_VBS_SEND_CERTS 9
+#define SEV_SNP_VBS_KEXEC_VALIDATE 10
+#define SEV_SNP_VBS_KEXEC_INVALIDATE 11
+
+/* ── low-level VTL call via SEV-SNP ───────────────────────────────────── */
+
+/*
+ * Issue a VBS call through the SVSM protocol.
+ *
+ * The CAA svsm_buffer is used to pass request/response data.
+ * RAX encodes the protocol (3 = VBS) and call ID.
+ * RCX carries the physical address of any auxiliary data buffer.
+ * RDX carries the size of the auxiliary data.
+ */
+static int sev_snp_vbs_call(u32 call_id, const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ struct svsm_call call = {};
+ int ret;
+
+ call.rax = SEV_SNP_VBS_CALL(call_id);
+ if (arg && arg_size) {
+ call.rcx = __pa(arg);
+ call.rdx = arg_size;
+ }
+ if (resp && resp_size) {
+ call.r8 = __pa(resp);
+ call.r9 = resp_size;
+ }
+
+ ret = svsm_perform_call_protocol(&call);
+ if (ret)
+ pr_err_ratelimited("vbs-sev-snp: call %u failed (%d)\n",
+ call_id, ret);
+ return ret;
+}
+
+static int sev_snp_vbs_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ return sev_snp_vbs_call(id, arg, arg_size, resp, resp_size);
+}
+
+/* ── memory protection ────────────────────────────────────────────────── */
+
+struct vbs_sev_snp_protect_args {
+ __u64 pfn;
+ __u64 nr_pages;
+ __u32 perms;
+} __packed;
+
+static int sev_snp_vbs_protect_memory(unsigned long pfn,
+ unsigned long nr_pages,
+ unsigned int perms)
+{
+ struct vbs_sev_snp_protect_args args = {
+ .pfn = pfn,
+ .nr_pages = nr_pages,
+ .perms = perms,
+ };
+
+ return sev_snp_vbs_call(SEV_SNP_VBS_PROTECT_MEMORY,
+ &args, sizeof(args), NULL, 0);
+}
+
+static int sev_snp_vbs_seal_kernel(void)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_SEAL_KERNEL, NULL, 0, NULL, 0);
+}
+
+/* ── module authentication ────────────────────────────────────────────── */
+
+static int sev_snp_vbs_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size)
+{
+ /*
+ * Module ELF may be large. Pass its physical address and size
+ * to VMPL0 so the SVSM can read it from the shared address space.
+ */
+ return sev_snp_vbs_call(SEV_SNP_VBS_VALIDATE_MODULE,
+ NULL, 0, NULL, 0);
+}
+
+static int sev_snp_vbs_set_module_perms(const struct module *mod)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_SET_MODULE_PERMS,
+ NULL, 0, NULL, 0);
+}
+
+static int sev_snp_vbs_unload_module(const struct module *mod)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_UNLOAD_MODULE, NULL, 0, NULL, 0);
+}
+
+/* ── key / certificate management ─────────────────────────────────────── */
+
+static int sev_snp_vbs_add_key(const void *key, size_t key_size,
+ unsigned int flags)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_ADD_KEY, key, key_size, NULL, 0);
+}
+
+static int sev_snp_vbs_revoke_key(const void *key_id, size_t id_size)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_REVOKE_KEY, key_id, id_size, NULL, 0);
+}
+
+static int sev_snp_vbs_send_certs(const void *certs, size_t certs_size)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_SEND_CERTS,
+ certs, certs_size, NULL, 0);
+}
+
+/* ── kexec validation ─────────────────────────────────────────────────── */
+
+static int sev_snp_vbs_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_KEXEC_VALIDATE, NULL, 0, NULL, 0);
+}
+
+static int sev_snp_vbs_kexec_invalidate(void)
+{
+ return sev_snp_vbs_call(SEV_SNP_VBS_KEXEC_INVALIDATE, NULL, 0, NULL, 0);
+}
+
+/* ── lifecycle ────────────────────────────────────────────────────────── */
+
+static int sev_snp_vbs_init(void)
+{
+ int ret;
+
+ ret = sev_snp_vbs_call(SEV_SNP_VBS_INIT, NULL, 0, NULL, 0);
+ if (ret) {
+ pr_err("vbs-sev-snp: VBS init failed (%d)\n", ret);
+ return ret;
+ }
+
+ pr_info("vbs-sev-snp: connected to SVSM at VMPL0\n");
+ return 0;
+}
+
+static void sev_snp_vbs_shutdown(void)
+{
+ sev_snp_vbs_call(SEV_SNP_VBS_SHUTDOWN, NULL, 0, NULL, 0);
+}
+
+/* ── ops table & registration ─────────────────────────────────────────── */
+
+static const struct vbs_ops sev_snp_vbs_ops = {
+ .name = "sev-snp",
+ .init = sev_snp_vbs_init,
+ .shutdown = sev_snp_vbs_shutdown,
+ .vtl_call = sev_snp_vbs_vtl_call,
+ .protect_memory = sev_snp_vbs_protect_memory,
+ .seal_kernel = sev_snp_vbs_seal_kernel,
+ .validate_module = sev_snp_vbs_validate_module,
+ .set_module_perms = sev_snp_vbs_set_module_perms,
+ .unload_module = sev_snp_vbs_unload_module,
+ .add_key = sev_snp_vbs_add_key,
+ .revoke_key = sev_snp_vbs_revoke_key,
+ .send_certs = sev_snp_vbs_send_certs,
+ .kexec_validate = sev_snp_vbs_kexec_validate,
+ .kexec_invalidate = sev_snp_vbs_kexec_invalidate,
+};
+
+/* ── detection & probe (called from probe.c) ──────────────────────────── */
+
+bool __init vbs_sev_snp_detect(void)
+{
+ if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
+ return false;
+
+ if (snp_vmpl == 0) {
+ pr_debug("vbs-sev-snp: running at VMPL0, no SVSM above us\n");
+ return false;
+ }
+
+ return true;
+}
+
+const struct vbs_ops *vbs_sev_snp_get_ops(void)
+{
+ return &sev_snp_vbs_ops;
+}
diff --git a/security/vbs/tdx.c b/security/vbs/tdx.c
new file mode 100644
index 000000000000..43e636e2b591
--- /dev/null
+++ b/security/vbs/tdx.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS backend — Intel TDX service TD
+ *
+ * Uses TDG.VP.VMCALL (TDVMCALL) to communicate between the main TD
+ * (plane-0) and a service TD (plane-1) that provides security services.
+ *
+ * Transport: TDVMCALL with a VBS-specific sub-function leaf. The VMM
+ * (QEMU / KVM) routes the call to the service TD, which
+ * shares memory with the main TD for request/response data.
+ *
+ * Note: Service TD support is still evolving in the TDX architecture.
+ * This backend provides the framework and will be updated as the
+ * inter-TD communication spec is finalised.
+ */
+
+#include "internal.h"
+
+#include <linux/cc_platform.h>
+#include <linux/mm.h>
+
+#include <asm/shared/tdx.h>
+#include <asm/tdx.h>
+
+/*
+ * VBS-specific TDVMCALL sub-function. Chosen from the vendor-specific
+ * range (>= 0x10010000) to avoid conflicts with the GHCI-defined leaves.
+ */
+#define TDVMCALL_VBS 0x10010000ULL
+
+/* VBS sub-commands passed in R12 */
+#define TDX_VBS_INIT 0
+#define TDX_VBS_SHUTDOWN 1
+#define TDX_VBS_PROTECT_MEMORY 2
+#define TDX_VBS_SEAL_KERNEL 3
+#define TDX_VBS_VALIDATE_MODULE 4
+#define TDX_VBS_SET_MODULE_PERMS 5
+#define TDX_VBS_UNLOAD_MODULE 6
+#define TDX_VBS_ADD_KEY 7
+#define TDX_VBS_REVOKE_KEY 8
+#define TDX_VBS_SEND_CERTS 9
+#define TDX_VBS_KEXEC_VALIDATE 10
+#define TDX_VBS_KEXEC_INVALIDATE 11
+
+/* ── shared-memory buffers ────────────────────────────────────────────── */
+
+/*
+ * Shared (decrypted) pages for passing request and response data between
+ * the main TD and the service TD. Marked shared via cc_mkdec() so the
+ * VMM and service TD can access them.
+ */
+static void *tdx_req_page;
+static void *tdx_resp_page;
+
+/* ── low-level VBS TDVMCALL ───────────────────────────────────────────── */
+
+/*
+ * Issue a VBS call to the service TD through the VMM.
+ *
+ * Register usage (TDVMCALL convention):
+ * R11 = sub-function leaf (TDVMCALL_VBS)
+ * R12 = VBS command ID
+ * R13 = physical address of request buffer (shared)
+ * R14 = physical address of response buffer (shared)
+ * R15 = request size
+ */
+static int tdx_vbs_call(u32 cmd, const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ struct tdx_module_args args = {};
+ u64 ret;
+
+ if (arg && arg_size) {
+ if (arg_size > PAGE_SIZE || !tdx_req_page)
+ return -E2BIG;
+ memcpy(tdx_req_page, arg, arg_size);
+ }
+
+ args.r11 = TDVMCALL_VBS;
+ args.r12 = cmd;
+ args.r13 = tdx_req_page ? cc_mkdec(virt_to_phys(tdx_req_page)) : 0;
+ args.r14 = tdx_resp_page ? cc_mkdec(virt_to_phys(tdx_resp_page)) : 0;
+ args.r15 = arg_size;
+
+ ret = __tdx_hypercall(&args);
+ if (ret) {
+ pr_err_ratelimited("vbs-tdx: TDVMCALL failed (0x%llx)\n", ret);
+ return -EIO;
+ }
+
+ /* R10 holds the VMM return status */
+ if (args.r10) {
+ pr_err_ratelimited("vbs-tdx: service TD returned 0x%llx\n",
+ args.r10);
+ return -EREMOTEIO;
+ }
+
+ if (resp && resp_size && tdx_resp_page) {
+ size_t copy = min_t(size_t, resp_size, PAGE_SIZE);
+
+ memcpy(resp, tdx_resp_page, copy);
+ }
+ return 0;
+}
+
+static int tdx_vbs_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ return tdx_vbs_call(id, arg, arg_size, resp, resp_size);
+}
+
+/* ── memory protection ────────────────────────────────────────────────── */
+
+struct vbs_tdx_protect_args {
+ __u64 pfn;
+ __u64 nr_pages;
+ __u32 perms;
+} __packed;
+
+static int tdx_vbs_protect_memory(unsigned long pfn, unsigned long nr_pages,
+ unsigned int perms)
+{
+ struct vbs_tdx_protect_args args = {
+ .pfn = pfn,
+ .nr_pages = nr_pages,
+ .perms = perms,
+ };
+
+ return tdx_vbs_call(TDX_VBS_PROTECT_MEMORY,
+ &args, sizeof(args), NULL, 0);
+}
+
+static int tdx_vbs_seal_kernel(void)
+{
+ return tdx_vbs_call(TDX_VBS_SEAL_KERNEL, NULL, 0, NULL, 0);
+}
+
+/* ── module authentication ────────────────────────────────────────────── */
+
+static int tdx_vbs_validate_module(const void *elf, size_t elf_size,
+ const void *sig, size_t sig_size)
+{
+ return tdx_vbs_call(TDX_VBS_VALIDATE_MODULE, NULL, 0, NULL, 0);
+}
+
+static int tdx_vbs_set_module_perms(const struct module *mod)
+{
+ return tdx_vbs_call(TDX_VBS_SET_MODULE_PERMS, NULL, 0, NULL, 0);
+}
+
+static int tdx_vbs_unload_module(const struct module *mod)
+{
+ return tdx_vbs_call(TDX_VBS_UNLOAD_MODULE, NULL, 0, NULL, 0);
+}
+
+/* ── key / certificate management ─────────────────────────────────────── */
+
+static int tdx_vbs_add_key(const void *key, size_t key_size,
+ unsigned int flags)
+{
+ return tdx_vbs_call(TDX_VBS_ADD_KEY, key, key_size, NULL, 0);
+}
+
+static int tdx_vbs_revoke_key(const void *key_id, size_t id_size)
+{
+ return tdx_vbs_call(TDX_VBS_REVOKE_KEY, key_id, id_size, NULL, 0);
+}
+
+static int tdx_vbs_send_certs(const void *certs, size_t certs_size)
+{
+ return tdx_vbs_call(TDX_VBS_SEND_CERTS, certs, certs_size, NULL, 0);
+}
+
+/* ── kexec validation ─────────────────────────────────────────────────── */
+
+static int tdx_vbs_kexec_validate(const void *kernel, size_t kernel_size,
+ const void *sig, size_t sig_size)
+{
+ return tdx_vbs_call(TDX_VBS_KEXEC_VALIDATE, NULL, 0, NULL, 0);
+}
+
+static int tdx_vbs_kexec_invalidate(void)
+{
+ return tdx_vbs_call(TDX_VBS_KEXEC_INVALIDATE, NULL, 0, NULL, 0);
+}
+
+/* ── lifecycle ────────────────────────────────────────────────────────── */
+
+static int tdx_vbs_init(void)
+{
+ int ret;
+
+ /*
+ * Allocate shared pages for inter-TD communication. These must
+ * be marked as shared (decrypted) so the service TD can read them.
+ */
+ tdx_req_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ tdx_resp_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (!tdx_req_page || !tdx_resp_page) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ /*
+ * Convert to shared pages. set_memory_decrypted() clears the
+ * encryption bit so the VMM / service TD can access these pages.
+ */
+ ret = set_memory_decrypted((unsigned long)tdx_req_page, 1);
+ if (ret)
+ goto fail;
+ ret = set_memory_decrypted((unsigned long)tdx_resp_page, 1);
+ if (ret)
+ goto fail_re_encrypt_req;
+
+ ret = tdx_vbs_call(TDX_VBS_INIT, NULL, 0, NULL, 0);
+ if (ret) {
+ pr_err("vbs-tdx: service TD init failed (%d)\n", ret);
+ goto fail_re_encrypt;
+ }
+
+ pr_info("vbs-tdx: connected to TDX service TD\n");
+ return 0;
+
+fail_re_encrypt:
+ set_memory_encrypted((unsigned long)tdx_resp_page, 1);
+fail_re_encrypt_req:
+ set_memory_encrypted((unsigned long)tdx_req_page, 1);
+fail:
+ free_page((unsigned long)tdx_req_page);
+ free_page((unsigned long)tdx_resp_page);
+ tdx_req_page = tdx_resp_page = NULL;
+ return ret;
+}
+
+static void tdx_vbs_shutdown(void)
+{
+ tdx_vbs_call(TDX_VBS_SHUTDOWN, NULL, 0, NULL, 0);
+
+ if (tdx_resp_page) {
+ set_memory_encrypted((unsigned long)tdx_resp_page, 1);
+ free_page((unsigned long)tdx_resp_page);
+ }
+ if (tdx_req_page) {
+ set_memory_encrypted((unsigned long)tdx_req_page, 1);
+ free_page((unsigned long)tdx_req_page);
+ }
+ tdx_req_page = tdx_resp_page = NULL;
+}
+
+/* ── ops table & registration ─────────────────────────────────────────── */
+
+static const struct vbs_ops tdx_vbs_ops = {
+ .name = "tdx-service-td",
+ .init = tdx_vbs_init,
+ .shutdown = tdx_vbs_shutdown,
+ .vtl_call = tdx_vbs_vtl_call,
+ .protect_memory = tdx_vbs_protect_memory,
+ .seal_kernel = tdx_vbs_seal_kernel,
+ .validate_module = tdx_vbs_validate_module,
+ .set_module_perms = tdx_vbs_set_module_perms,
+ .unload_module = tdx_vbs_unload_module,
+ .add_key = tdx_vbs_add_key,
+ .revoke_key = tdx_vbs_revoke_key,
+ .send_certs = tdx_vbs_send_certs,
+ .kexec_validate = tdx_vbs_kexec_validate,
+ .kexec_invalidate = tdx_vbs_kexec_invalidate,
+};
+
+/* ── detection & probe (called from probe.c) ──────────────────────────── */
+
+bool __init vbs_tdx_detect(void)
+{
+ return cc_platform_has(CC_ATTR_GUEST_TDX);
+}
+
+const struct vbs_ops *vbs_tdx_get_ops(void)
+{
+ return &tdx_vbs_ops;
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 13/42] Add a inter-plane communication mechanism through KVM. - model this to use a single page similar to SEV-SNP
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (11 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 12/42] Add a Virtualization Based Security (VBS) framework. - Add backends for AMD SEV-SNP, Intel TDX, Arm CCA and KVM Planes. - Support VTL on Hyper-V in addition to Planes on KVM Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 14/42] KVM: Add per-plane memory attribute support for cross-plane EPT protection Sriram Nambakam
` (28 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
security/vbs/kvm_planes.c | 102 +++++++++++++++++++-------------------
1 file changed, 51 insertions(+), 51 deletions(-)
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
index 3526f7c429c3..3eec3abb56ee 100644
--- a/security/vbs/kvm_planes.c
+++ b/security/vbs/kvm_planes.c
@@ -31,26 +31,30 @@
*/
#define KVM_HC_VBS_VTL_CALL 15
-/* ── shared-memory request / response layout ──────────────────────────── */
+/* ── shared-memory calling area (modelled after the SVSM CAA) ─────── */
-struct vbs_kvm_request {
- __u32 call_id; /* enum vbs_call_id */
- __u32 arg_size; /* bytes of payload following this hdr */
- __u8 payload[]; /* variable-length argument data */
+/*
+ * Single shared page used for both request and response data.
+ * The protocol is synchronous: plane-0 writes the request, issues a
+ * hypercall, blocks until QEMU returns, then reads the response from
+ * the same page. No concurrent access is possible.
+ *
+ * Layout (within one 4 KiB page):
+ * [ call_pending | call_id | status | arg_size | resp_size | buffer ]
+ */
+struct vbs_kvm_ca {
+ __u8 call_pending; /* 1 while call is in flight */
+ __u8 rsvd[3];
+ __u32 call_id; /* enum vbs_call_id (set by caller) */
+ __s32 status; /* return code (set by responder) */
+ __u32 arg_size; /* request payload size */
+ __u32 resp_size; /* response payload size */
+ __u8 buffer[]; /* request data in, response data out */
} __packed;
-struct vbs_kvm_response {
- __s32 status; /* 0 = success, negative errno */
- __u32 resp_size; /* bytes of payload following this hdr */
- __u8 payload[]; /* variable-length response data */
-} __packed;
+#define VBS_CA_BUF_SIZE (PAGE_SIZE - sizeof(struct vbs_kvm_ca))
-/*
- * A single page is used for each direction. That gives ~4 KiB of
- * payload per call, which is enough for all current VBS operations.
- */
-static void *kvm_req_page; /* request (plane-0 writes, plane-1 reads) */
-static void *kvm_resp_page; /* response (plane-1 writes, plane-0 reads) */
+static void *kvm_ca_page; /* single calling-area page */
/* ── low-level VTL call ───────────────────────────────────────────────── */
@@ -58,41 +62,44 @@ static int kvm_planes_vtl_call(enum vbs_call_id id,
const void *arg, size_t arg_size,
void *resp, size_t resp_size)
{
- struct vbs_kvm_request *req;
- struct vbs_kvm_response *rsp;
+ struct vbs_kvm_ca *ca;
long hc_ret;
- if (!kvm_req_page || !kvm_resp_page)
+ if (!kvm_ca_page)
return -ENOMEM;
- if (arg_size > PAGE_SIZE - sizeof(*req))
+ if (arg_size > VBS_CA_BUF_SIZE)
return -E2BIG;
- /* Build request in the shared page */
- req = kvm_req_page;
- req->call_id = id;
- req->arg_size = arg_size;
+ ca = kvm_ca_page;
+
+ /* Build request */
+ ca->call_id = id;
+ ca->arg_size = arg_size;
+ ca->status = 0;
+ ca->resp_size = 0;
if (arg_size && arg)
- memcpy(req->payload, arg, arg_size);
+ memcpy(ca->buffer, arg, arg_size);
+ ca->call_pending = 1;
+
+ /* Issue hypercall: pass physical address of the calling area */
+ hc_ret = kvm_hypercall1(KVM_HC_VBS_VTL_CALL,
+ virt_to_phys(kvm_ca_page));
+ ca->call_pending = 0;
- /* Issue hypercall: pass physical addresses of req & resp pages */
- hc_ret = kvm_hypercall2(KVM_HC_VBS_VTL_CALL,
- virt_to_phys(kvm_req_page),
- virt_to_phys(kvm_resp_page));
if (hc_ret) {
pr_err_ratelimited("vbs-kvm: hypercall failed (%ld)\n", hc_ret);
return -EIO;
}
- /* Read response */
- rsp = kvm_resp_page;
- if (rsp->status)
- return rsp->status;
+ if (ca->status)
+ return ca->status;
- if (resp && resp_size) {
- size_t copy = min_t(size_t, resp_size, rsp->resp_size);
+ /* Read response from the same buffer */
+ if (resp && resp_size && ca->resp_size) {
+ size_t copy = min_t(size_t, resp_size, ca->resp_size);
- memcpy(resp, rsp->payload, copy);
+ memcpy(resp, ca->buffer, copy);
}
return 0;
}
@@ -192,34 +199,27 @@ static int kvm_planes_init(void)
{
int ret;
- kvm_req_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
- kvm_resp_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
- if (!kvm_req_page || !kvm_resp_page) {
- ret = -ENOMEM;
- goto fail;
- }
+ kvm_ca_page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO);
+ if (!kvm_ca_page)
+ return -ENOMEM;
ret = kvm_planes_vtl_call(VBS_CALL_INIT, NULL, 0, NULL, 0);
if (ret) {
pr_err("vbs-kvm: plane-1 INIT call failed (%d)\n", ret);
- goto fail;
+ free_page((unsigned long)kvm_ca_page);
+ kvm_ca_page = NULL;
+ return ret;
}
pr_info("vbs-kvm: connected to plane-1 secure kernel\n");
return 0;
-fail:
- free_page((unsigned long)kvm_req_page);
- free_page((unsigned long)kvm_resp_page);
- kvm_req_page = kvm_resp_page = NULL;
- return ret;
}
static void kvm_planes_shutdown(void)
{
kvm_planes_vtl_call(VBS_CALL_SHUTDOWN, NULL, 0, NULL, 0);
- free_page((unsigned long)kvm_req_page);
- free_page((unsigned long)kvm_resp_page);
- kvm_req_page = kvm_resp_page = NULL;
+ free_page((unsigned long)kvm_ca_page);
+ kvm_ca_page = NULL;
}
/* ── ops table & registration ─────────────────────────────────────────── */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 14/42] KVM: Add per-plane memory attribute support for cross-plane EPT protection
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (12 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 13/42] Add a inter-plane communication mechanism through KVM. - model this to use a single page similar to SEV-SNP Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 15/42] KVM: x86: Add KVM_HC_VBS_VTL_CALL hypercall for VBS inter-plane calls Sriram Nambakam
` (27 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Extend the KVM memory attributes framework to support per-plane
R/W/X permission control, enabling a higher-privilege plane (e.g.,
plane-1 / secure kernel) to restrict a lower-privilege plane's
(e.g., plane-0) EPT permissions.
This is the KVM equivalent of the AMD SEV-SNP RMP (Reverse Map Table):
each plane has its own mem_attr_array, and attributes like NO_WRITE
and NO_EXEC are enforced by filtering pte_access bits during SPTE
creation.
Changes:
- include/uapi/linux/kvm.h: Add KVM_MEMORY_ATTRIBUTE_NO_WRITE (bit 4)
and KVM_MEMORY_ATTRIBUTE_NO_EXEC (bit 5). Add struct
kvm_plane_memory_attributes and KVM_SET_PLANE_MEMORY_ATTRIBUTES
ioctl (0xd6) for targeting a specific plane's address space.
- virt/kvm/kvm_main.c: Extend kvm_supported_mem_attributes() to
return NO_WRITE|NO_EXEC when CONFIG_KVM_MAX_NR_VCPU_PLANES is
enabled. Add KVM_SET_PLANE_MEMORY_ATTRIBUTES ioctl handler that
validates the target plane and delegates to the existing
kvm_vm_ioctl_set_mem_attributes() infrastructure.
- arch/x86/kvm/mmu/spte.h: Add kvm_plane_filter_pte_access() helper
that reads the plane's mem_attr_array for a GFN and strips W/X
from pte_access when NO_WRITE/NO_EXEC are set.
- arch/x86/kvm/mmu/tdp_mmu.c, arch/x86/kvm/mmu/mmu.c: Wire
kvm_plane_filter_pte_access() into both TDP and shadow MMU SPTE
creation paths, filtering pte_access before calling make_spte().
---
arch/x86/kvm/mmu/mmu.c | 4 +++-
arch/x86/kvm/mmu/spte.h | 33 +++++++++++++++++++++++++++++++++
arch/x86/kvm/mmu/tdp_mmu.c | 4 +++-
include/uapi/linux/kvm.h | 23 +++++++++++++++++++++++
4 files changed, 62 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index a0d9a0a33c5f..3b861a42a712 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -3107,7 +3107,9 @@ static int mmu_set_spte(struct kvm_vcpu *vcpu, struct kvm_memory_slot *slot,
return RET_PF_EMULATE;
}
- wrprot = make_spte(vcpu, sp, slot, pte_access, gfn, pfn, *sptep, prefetch,
+ wrprot = make_spte(vcpu, sp, slot,
+ kvm_plane_filter_pte_access(vcpu, gfn, pte_access),
+ gfn, pfn, *sptep, prefetch,
false, host_writable, &spte);
if (*sptep == spte) {
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 13eea94dd212..421836fd3932 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -579,4 +579,37 @@ static inline u64 restore_acc_track_spte(u64 spte)
void __init kvm_mmu_spte_module_init(void);
void kvm_mmu_reset_all_pte_masks(void);
+/*
+ * Apply per-plane memory protection attributes to pte_access.
+ * If the plane's mem_attr_array has NO_WRITE or NO_EXEC set for a GFN,
+ * strip the corresponding access bits before building the SPTE.
+ */
+#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
+static inline unsigned int kvm_plane_filter_pte_access(struct kvm_vcpu *vcpu,
+ gfn_t gfn,
+ unsigned int pte_access)
+{
+ struct kvm_plane *plane = vcpu_to_plane(vcpu);
+ unsigned long attrs;
+
+ if (!plane)
+ return pte_access;
+
+ attrs = kvm_get_plane_memory_attributes(plane, gfn);
+ if (attrs & KVM_MEMORY_ATTRIBUTE_NO_WRITE)
+ pte_access &= ~ACC_WRITE_MASK;
+ if (attrs & KVM_MEMORY_ATTRIBUTE_NO_EXEC)
+ pte_access &= ~ACC_EXEC_MASK;
+
+ return pte_access;
+}
+#else
+static inline unsigned int kvm_plane_filter_pte_access(struct kvm_vcpu *vcpu,
+ gfn_t gfn,
+ unsigned int pte_access)
+{
+ return pte_access;
+}
+#endif
+
#endif
diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
index 4503558211fd..0603445377aa 100644
--- a/arch/x86/kvm/mmu/tdp_mmu.c
+++ b/arch/x86/kvm/mmu/tdp_mmu.c
@@ -1140,7 +1140,9 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
if (unlikely(!fault->slot))
new_spte = make_mmio_spte(vcpu, iter->gfn, sp->role.access);
else
- wrprot = make_spte(vcpu, sp, fault->slot, sp->role.access, iter->gfn,
+ wrprot = make_spte(vcpu, sp, fault->slot,
+ kvm_plane_filter_pte_access(vcpu, iter->gfn, sp->role.access),
+ iter->gfn,
fault->pfn, iter->old_spte, fault->prefetch,
false, fault->map_writable, &new_spte);
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index de670bd836bf..82189353ef35 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1687,6 +1687,29 @@ struct kvm_memory_attributes {
#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)
+/*
+ * Per-plane memory protection attributes (VM planes / VBS).
+ * These control EPT R/W/X permissions enforced by the hypervisor on
+ * behalf of a higher-privilege plane (e.g., plane-1 restricting plane-0).
+ */
+#define KVM_MEMORY_ATTRIBUTE_NO_WRITE (1ULL << 4)
+#define KVM_MEMORY_ATTRIBUTE_NO_EXEC (1ULL << 5)
+
+/*
+ * Set memory attributes on a specific plane's address space.
+ * Used by a higher-privilege plane to restrict a lower-privilege plane's
+ * EPT permissions (e.g., plane-1 making plane-0 kernel text read-only).
+ */
+struct kvm_plane_memory_attributes {
+ __u32 plane; /* target plane index */
+ __u32 flags; /* must be 0 */
+ __u64 address; /* GPA (page-aligned) */
+ __u64 size; /* size in bytes (page-aligned) */
+ __u64 attributes; /* KVM_MEMORY_ATTRIBUTE_NO_WRITE / NO_EXEC */
+};
+
+#define KVM_SET_PLANE_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd6, struct kvm_plane_memory_attributes)
+
#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
#define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 15/42] KVM: x86: Add KVM_HC_VBS_VTL_CALL hypercall for VBS inter-plane calls
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (13 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 14/42] KVM: Add per-plane memory attribute support for cross-plane EPT protection Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 16/42] vbs: Add HEKI kernel sealing and fix KVM plane memory attribute guards Sriram Nambakam
` (26 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Define KVM_HC_VBS_VTL_CALL (hypercall 15) in the UAPI header and wire
it into the KVM x86 hypercall exit path so it reaches QEMU userspace.
This hypercall is used by the plane-0 guest VBS subsystem to issue
synchronous calls to the plane-1 secure kernel via a shared calling-area
(CAA) page, following the same pattern as KVM_HC_VM_PLANES_CONFIG/
ACTIVATE.
Changes:
- include/uapi/linux/kvm_para.h: Define KVM_HC_VBS_VTL_CALL = 15
- arch/x86/kvm/x86.c: Add to KVM_EXIT_HYPERCALL_VALID_MASK and to the
userspace-exit case in ____kvm_emulate_hypercall()
- security/vbs/kvm_planes.c: Remove local #define of KVM_HC_VBS_VTL_CALL,
add #include <linux/kvm_para.h> to pick up the UAPI definition
---
arch/x86/kvm/x86.c | 6 ++++--
include/uapi/linux/kvm_para.h | 1 +
security/vbs/kvm_planes.c | 8 +-------
3 files changed, 6 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index b7256f155bea..4b99016fe536 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -121,7 +121,8 @@ static u64 __read_mostly efer_reserved_bits = ~((u64)EFER_SCE);
#define KVM_EXIT_HYPERCALL_VALID_MASK (BIT(KVM_HC_MAP_GPA_RANGE) | \
BIT(KVM_HC_VM_PLANES_CONFIG) | \
- BIT(KVM_HC_VM_PLANES_ACTIVATE))
+ BIT(KVM_HC_VM_PLANES_ACTIVATE) | \
+ BIT(KVM_HC_VBS_VTL_CALL))
#define KVM_CAP_PMU_VALID_MASK KVM_PMU_CAP_DISABLE
@@ -10533,7 +10534,8 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
return 0;
}
case KVM_HC_VM_PLANES_CONFIG:
- case KVM_HC_VM_PLANES_ACTIVATE: {
+ case KVM_HC_VM_PLANES_ACTIVATE:
+ case KVM_HC_VBS_VTL_CALL: {
ret = -KVM_ENOSYS;
if (!user_exit_on_hypercall(vcpu->kvm, nr))
break;
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 1b097f7ed937..1703238952fb 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -32,6 +32,7 @@
#define KVM_HC_MAP_GPA_RANGE 12
#define KVM_HC_VM_PLANES_CONFIG 13
#define KVM_HC_VM_PLANES_ACTIVATE 14
+#define KVM_HC_VBS_VTL_CALL 15
/*
* hypercalls use architecture specific
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
index 3eec3abb56ee..07a004712e9f 100644
--- a/security/vbs/kvm_planes.c
+++ b/security/vbs/kvm_planes.c
@@ -22,15 +22,9 @@
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
+#include <linux/kvm_para.h>
#include <asm/kvm_para.h>
-/* ── hypercall numbers for VBS VTL calls (plane-0 → plane-1) ──────────── */
-/*
- * These extend the existing KVM_HC_* numbering. The host (KVM + QEMU)
- * intercepts them and routes them to the secure-kernel plane.
- */
-#define KVM_HC_VBS_VTL_CALL 15
-
/* ── shared-memory calling area (modelled after the SVSM CAA) ─────── */
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 16/42] vbs: Add HEKI kernel sealing and fix KVM plane memory attribute guards
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (14 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 15/42] KVM: x86: Add KVM_HC_VBS_VTL_CALL hypercall for VBS inter-plane calls Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 17/42] vbs: Add module authentication via VBS/HEKI Sriram Nambakam
` (25 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Implement Hypervisor-Enforced Kernel Integrity (HEKI) — the plane-0
guest kernel automatically seals its text and rodata sections at
late_initcall time by sending their GPAs to QEMU via the VBS VTL
call mechanism.
Guest-side changes:
- security/vbs/core.c: Add vbs_heki_late_init() as a late_initcall
that calls ops->init() to set up the VBS backend (allocate the
shared CAA page, send VBS_CALL_INIT), then calls vbs_seal_kernel()
to request kernel text/rodata protection.
- security/vbs/kvm_planes.c: Implement kvm_planes_seal_kernel() to
build a vbs_seal_kernel_req with page-aligned text/rodata GPAs
and CR3, sent via VBS_CALL_SEAL_KERNEL to QEMU.
- security/vbs/heki.h (new): Shared HEKI data structures
(vbs_seal_kernel_req, vbs_protect_memory_req) and x86-64 page
table walker callback interface.
- security/vbs/heki.c (new): x86-64 4-level page table walker
for plane-1 auditing of plane-0 mappings. Classifies pages as
TEXT/RODATA/DATA_RW/DATA_RX based on PTE permission bits.
- security/vbs/Kconfig: Add CONFIG_VBS_HEKI option.
- security/vbs/Makefile: Build heki.o when CONFIG_VBS_HEKI=y.
Host-side fix:
- virt/kvm/kvm_main.c: Replace CONFIG_KVM_MAX_NR_VCPU_PLANES
(which had no Kconfig definition and was never set) with
CONFIG_VM_PLANES in the three #ifdef guards protecting
KVM_SET_PLANE_MEMORY_ATTRIBUTES ioctl and NO_WRITE/NO_EXEC
attribute support. Without this fix the ioctl returned -ENOTTY.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
security/vbs/Kconfig | 14 ++
security/vbs/Makefile | 1 +
security/vbs/core.c | 32 +++++
security/vbs/heki.c | 287 ++++++++++++++++++++++++++++++++++++++
security/vbs/heki.h | 83 +++++++++++
security/vbs/kvm_planes.c | 19 ++-
6 files changed, 435 insertions(+), 1 deletion(-)
create mode 100644 security/vbs/heki.c
create mode 100644 security/vbs/heki.h
diff --git a/security/vbs/Kconfig b/security/vbs/Kconfig
index 3d9fb104b1fc..0fdbfbc7a795 100644
--- a/security/vbs/Kconfig
+++ b/security/vbs/Kconfig
@@ -15,6 +15,20 @@ config VBS
If unsure, say N.
+config VBS_HEKI
+ bool "HEKI: Hypervisor-Enforced Kernel Integrity"
+ depends on VBS && X86_64
+ help
+ Enable the HEKI subsystem which provides:
+ - x86-64 page table walker for auditing guest kernel mappings
+ - Kernel seal support (make kernel text/rodata immutable via
+ EPT permission enforcement)
+
+ This code runs in plane-1 (secure kernel) to inspect and
+ protect plane-0's address space.
+
+ If unsure, say N.
+
config VBS_KVM_PLANES
bool "VBS backend: KVM software planes"
depends on VBS && KVM_GUEST
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
index 4f0f26ef4f71..e33052ccde2d 100644
--- a/security/vbs/Makefile
+++ b/security/vbs/Makefile
@@ -2,6 +2,7 @@
obj-$(CONFIG_VBS) += vbs.o
vbs-y := core.o probe.o
+vbs-$(CONFIG_VBS_HEKI) += heki.o
obj-$(CONFIG_VBS_KVM_PLANES) += kvm_planes.o
obj-$(CONFIG_VBS_SEV_SNP) += sev_snp.o
obj-$(CONFIG_VBS_TDX) += tdx.o
diff --git a/security/vbs/core.c b/security/vbs/core.c
index 352590d88136..16b5329964f9 100644
--- a/security/vbs/core.c
+++ b/security/vbs/core.c
@@ -164,3 +164,35 @@ int vbs_kexec_invalidate(void)
return ops->kexec_invalidate();
}
EXPORT_SYMBOL_GPL(vbs_kexec_invalidate);
+
+/* ── HEKI: automatic kernel sealing at late init ──────────────────────── */
+
+static int __init vbs_heki_late_init(void)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+ int ret;
+
+ if (!ops) {
+ pr_debug("vbs: HEKI: no backend, skipping kernel seal\n");
+ return 0;
+ }
+
+ /* Initialize the backend (allocates shared memory, etc.) */
+ if (ops->init) {
+ ret = ops->init();
+ if (ret) {
+ pr_warn("vbs: HEKI: backend init failed (%d)\n", ret);
+ return 0;
+ }
+ }
+
+ pr_info("vbs: HEKI: sealing kernel text and rodata\n");
+ ret = vbs_seal_kernel();
+ if (ret)
+ pr_warn("vbs: HEKI: seal_kernel failed (%d)\n", ret);
+ else
+ pr_info("vbs: HEKI: kernel sealed successfully\n");
+
+ return 0;
+}
+late_initcall(vbs_heki_late_init);
diff --git a/security/vbs/heki.c b/security/vbs/heki.c
new file mode 100644
index 000000000000..8b4c4e3b170b
--- /dev/null
+++ b/security/vbs/heki.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HEKI — Hypervisor-Enforced Kernel Integrity
+ *
+ * x86-64 page table walker and kernel protection logic.
+ *
+ * The page table walker is designed to be called from plane-1 (the secure
+ * kernel) to audit plane-0's page tables. It is parameterised with a
+ * read_gpa() callback so it can work both in-kernel (for plane-1 with
+ * direct GPA access) and from QEMU (future, for host-side auditing).
+ *
+ * The seal_kernel helper runs in plane-0 and sends the kernel text/rodata
+ * GPA ranges to the secure side via the VBS VTL call mechanism.
+ */
+
+#include "heki.h"
+#include "internal.h"
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <asm/sections.h>
+
+#ifdef CONFIG_X86_64
+#include <asm/page.h>
+
+/* ── x86-64 page table constants ──────────────────────────────────────── */
+
+#define PT_ENTRIES 512
+#define PT_ENTRY_SIZE 8
+
+/* PTE bit positions */
+#define PTE_PRESENT BIT_ULL(0)
+#define PTE_WRITABLE BIT_ULL(1)
+#define PTE_USER BIT_ULL(2)
+#define PTE_PS BIT_ULL(7) /* page size (huge page) */
+#define PTE_NX BIT_ULL(63) /* no-execute */
+
+/* Physical address mask for 4-level paging (bits 12..51) */
+#define PTE_ADDR_MASK 0x000FFFFFFFFFF000ULL
+
+/* Page sizes */
+#define PAGE_SIZE_4K (1UL << 12)
+#define PAGE_SIZE_2M (1UL << 21)
+#define PAGE_SIZE_1G (1UL << 30)
+
+/* Virtual address extraction helpers */
+static inline unsigned int pml4_index(unsigned long va)
+{
+ return (va >> 39) & 0x1FF;
+}
+
+static inline unsigned int pdpt_index(unsigned long va)
+{
+ return (va >> 30) & 0x1FF;
+}
+
+static inline unsigned int pd_index(unsigned long va)
+{
+ return (va >> 21) & 0x1FF;
+}
+
+static inline unsigned int pt_index(unsigned long va)
+{
+ return (va >> 12) & 0x1FF;
+}
+
+/*
+ * Classify a page based on its PTE permission bits.
+ */
+static enum heki_page_class classify_pte(u64 pte)
+{
+ bool writable = !!(pte & PTE_WRITABLE);
+ bool executable = !(pte & PTE_NX);
+
+ if (executable && !writable)
+ return HEKI_PAGE_TEXT;
+ if (!executable && !writable)
+ return HEKI_PAGE_RODATA;
+ if (!executable && writable)
+ return HEKI_PAGE_DATA_RW;
+ /* executable + writable — W^X violation */
+ return HEKI_PAGE_DATA_RX;
+}
+
+/*
+ * Read a single page table entry from guest physical memory.
+ */
+static int read_pte(u64 table_gpa, unsigned int index,
+ int (*read_gpa)(u64, void *, size_t, void *),
+ void *ctx, u64 *pte_out)
+{
+ u64 entry_gpa = table_gpa + (u64)index * PT_ENTRY_SIZE;
+
+ return read_gpa(entry_gpa, pte_out, sizeof(*pte_out), ctx);
+}
+
+/*
+ * Walk a page table (PT) level — 4K pages.
+ */
+static int walk_pt(u64 pt_gpa, unsigned long va_base,
+ int (*read_gpa)(u64, void *, size_t, void *), void *ctx,
+ unsigned long va_start, unsigned long va_end,
+ heki_walk_cb cb, void *priv)
+{
+ unsigned int start_idx, end_idx, i;
+ int ret;
+
+ start_idx = (va_start > va_base) ? pt_index(va_start) : 0;
+ end_idx = (va_end && va_end < va_base + PT_ENTRIES * PAGE_SIZE_4K)
+ ? pt_index(va_end - 1) : PT_ENTRIES - 1;
+
+ for (i = start_idx; i <= end_idx; i++) {
+ u64 pte;
+ unsigned long va = va_base + (unsigned long)i * PAGE_SIZE_4K;
+
+ ret = read_pte(pt_gpa, i, read_gpa, ctx, &pte);
+ if (ret)
+ return ret;
+ if (!(pte & PTE_PRESENT))
+ continue;
+
+ ret = cb(va, pte & PTE_ADDR_MASK, PAGE_SIZE_4K,
+ classify_pte(pte), priv);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * Walk a page directory (PD) level — 2M huge pages or recurse into PT.
+ */
+static int walk_pd(u64 pd_gpa, unsigned long va_base,
+ int (*read_gpa)(u64, void *, size_t, void *), void *ctx,
+ unsigned long va_start, unsigned long va_end,
+ heki_walk_cb cb, void *priv)
+{
+ unsigned int start_idx, end_idx, i;
+ int ret;
+
+ start_idx = (va_start > va_base) ? pd_index(va_start) : 0;
+ end_idx = (va_end && va_end < va_base + (unsigned long)PT_ENTRIES * PAGE_SIZE_2M)
+ ? pd_index(va_end - 1) : PT_ENTRIES - 1;
+
+ for (i = start_idx; i <= end_idx; i++) {
+ u64 pde;
+ unsigned long va = va_base + (unsigned long)i * PAGE_SIZE_2M;
+
+ ret = read_pte(pd_gpa, i, read_gpa, ctx, &pde);
+ if (ret)
+ return ret;
+ if (!(pde & PTE_PRESENT))
+ continue;
+
+ if (pde & PTE_PS) {
+ /* 2M huge page */
+ ret = cb(va, pde & PTE_ADDR_MASK, PAGE_SIZE_2M,
+ classify_pte(pde), priv);
+ if (ret)
+ return ret;
+ } else {
+ ret = walk_pt(pde & PTE_ADDR_MASK, va,
+ read_gpa, ctx, va_start, va_end,
+ cb, priv);
+ if (ret)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Walk a page directory pointer table (PDPT) — 1G huge pages or recurse.
+ */
+static int walk_pdpt(u64 pdpt_gpa, unsigned long va_base,
+ int (*read_gpa)(u64, void *, size_t, void *), void *ctx,
+ unsigned long va_start, unsigned long va_end,
+ heki_walk_cb cb, void *priv)
+{
+ unsigned int start_idx, end_idx, i;
+ int ret;
+
+ start_idx = (va_start > va_base) ? pdpt_index(va_start) : 0;
+ end_idx = (va_end && va_end < va_base + (unsigned long)PT_ENTRIES * PAGE_SIZE_1G)
+ ? pdpt_index(va_end - 1) : PT_ENTRIES - 1;
+
+ for (i = start_idx; i <= end_idx; i++) {
+ u64 pdpte;
+ unsigned long va = va_base + (unsigned long)i * PAGE_SIZE_1G;
+
+ ret = read_pte(pdpt_gpa, i, read_gpa, ctx, &pdpte);
+ if (ret)
+ return ret;
+ if (!(pdpte & PTE_PRESENT))
+ continue;
+
+ if (pdpte & PTE_PS) {
+ /* 1G huge page */
+ ret = cb(va, pdpte & PTE_ADDR_MASK, PAGE_SIZE_1G,
+ classify_pte(pdpte), priv);
+ if (ret)
+ return ret;
+ } else {
+ ret = walk_pd(pdpte & PTE_ADDR_MASK, va,
+ read_gpa, ctx, va_start, va_end,
+ cb, priv);
+ if (ret)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/**
+ * heki_walk_x86_tables - walk x86-64 4-level page tables
+ * @cr3: value of CR3 (page table root physical address)
+ * @read_gpa: callback to read bytes from a guest physical address
+ * @read_ctx: opaque context passed to read_gpa
+ * @va_start: start of virtual address range (0 = from beginning)
+ * @va_end: end of virtual address range (0 = to end)
+ * @cb: callback invoked for each present page
+ * @priv: opaque context passed to cb
+ *
+ * Walks the full PML4 → PDPT → PD → PT hierarchy, invoking @cb for
+ * every present page (4K, 2M, or 1G) within [va_start, va_end).
+ *
+ * Returns 0 on success, or the first non-zero return from @cb / @read_gpa.
+ */
+int heki_walk_x86_tables(unsigned long cr3,
+ int (*read_gpa)(u64 gpa, void *buf, size_t len,
+ void *ctx),
+ void *read_ctx,
+ unsigned long va_start, unsigned long va_end,
+ heki_walk_cb cb, void *priv)
+{
+ u64 pml4_gpa = cr3 & PTE_ADDR_MASK;
+ unsigned int i;
+ int ret;
+
+ if (!read_gpa || !cb)
+ return -EINVAL;
+
+ /*
+ * Walk PML4 entries. Each PML4 entry covers 512 GB.
+ * For the kernel half of the address space on x86-64,
+ * entries 256..511 map the kernel virtual addresses
+ * (0xffff800000000000 and above).
+ */
+ for (i = 0; i < PT_ENTRIES; i++) {
+ u64 pml4e;
+ /* Each PML4 entry covers 512 GiB */
+ unsigned long va_base = (unsigned long)i << 39;
+
+ /*
+ * Sign-extend for canonical addresses: entries 256..511
+ * map the upper half (kernel space).
+ */
+ if (i >= 256)
+ va_base |= 0xFFFF000000000000UL;
+
+ /* Skip entries outside the requested range */
+ if (va_end && va_base >= va_end)
+ break;
+ if (va_start) {
+ unsigned long entry_end = va_base +
+ (1UL << 39) - 1;
+ if (entry_end < va_start)
+ continue;
+ }
+
+ ret = read_pte(pml4_gpa, i, read_gpa, read_ctx, &pml4e);
+ if (ret)
+ return ret;
+ if (!(pml4e & PTE_PRESENT))
+ continue;
+
+ ret = walk_pdpt(pml4e & PTE_ADDR_MASK, va_base,
+ read_gpa, read_ctx, va_start, va_end,
+ cb, priv);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+#endif /* CONFIG_X86_64 */
diff --git a/security/vbs/heki.h b/security/vbs/heki.h
new file mode 100644
index 000000000000..fee986de351a
--- /dev/null
+++ b/security/vbs/heki.h
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * HEKI — Hypervisor-Enforced Kernel Integrity
+ *
+ * Shared data structures between the guest kernel (plane-0) and the
+ * VBS secure kernel / QEMU dispatcher. These structs are placed in
+ * the VBS CAA page buffer and must be kept in sync with the QEMU-side
+ * definitions.
+ */
+#ifndef _VBS_HEKI_H
+#define _VBS_HEKI_H
+
+#include <linux/types.h>
+
+/*
+ * VBS_CALL_PROTECT_MEMORY payload — request EPT permission changes on
+ * a contiguous GPA range from the perspective of the calling plane.
+ */
+struct vbs_protect_memory_req {
+ __u64 gpa; /* guest-physical address (page-aligned) */
+ __u64 size; /* region size in bytes (page-aligned) */
+ __u32 perms; /* desired permissions: VBS_MEM_* flags */
+ __u32 flags; /* reserved, must be 0 */
+} __packed;
+
+/*
+ * VBS_CALL_SEAL_KERNEL payload — plane-0 sends the GPAs of its kernel
+ * text and rodata sections so that the secure side can make them
+ * immutable (NO_WRITE in the lower plane's EPT).
+ */
+struct vbs_seal_kernel_req {
+ __u64 text_gpa; /* _stext physical address */
+ __u64 text_size; /* _etext - _stext */
+ __u64 rodata_gpa; /* __start_rodata physical address */
+ __u64 rodata_size; /* __end_rodata - __start_rodata */
+ __u64 cr3; /* plane-0 kernel CR3 for verification */
+} __packed;
+
+/* ── x86-64 page table walker (for plane-1 auditing) ─────────────────── */
+
+/* Classification of a guest-physical page based on page table walk */
+enum heki_page_class {
+ HEKI_PAGE_UNMAPPED = 0,
+ HEKI_PAGE_TEXT = 1, /* executable, read-only (kernel text) */
+ HEKI_PAGE_RODATA = 2, /* non-executable, read-only */
+ HEKI_PAGE_DATA_RW = 3, /* non-executable, read-write */
+ HEKI_PAGE_DATA_RX = 4, /* executable, read-write (DANGEROUS) */
+};
+
+/*
+ * Callback invoked for each mapped page during a page table walk.
+ * @va: virtual address of the page
+ * @pa: guest-physical address of the page
+ * @size: page size (4K, 2M, or 1G)
+ * @pclass: classification based on PTE permission bits
+ * @priv: opaque context from the caller
+ *
+ * Return 0 to continue walking, non-zero to stop.
+ */
+typedef int (*heki_walk_cb)(unsigned long va, unsigned long pa,
+ unsigned long size, enum heki_page_class pclass,
+ void *priv);
+
+#ifdef CONFIG_X86_64
+/*
+ * Walk x86-64 4-level page tables starting from @cr3.
+ * @read_gpa: function to read @len bytes from guest physical address @gpa
+ * into @buf. Returns 0 on success.
+ * @va_start, @va_end: virtual address range to walk (0 for full walk)
+ * @cb: callback invoked for each mapped page
+ * @priv: opaque context passed to the callback
+ *
+ * Returns 0 on success, negative errno on failure.
+ */
+int heki_walk_x86_tables(unsigned long cr3,
+ int (*read_gpa)(u64 gpa, void *buf, size_t len,
+ void *ctx),
+ void *read_ctx,
+ unsigned long va_start, unsigned long va_end,
+ heki_walk_cb cb, void *priv);
+#endif /* CONFIG_X86_64 */
+
+#endif /* _VBS_HEKI_H */
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
index 07a004712e9f..293c960c0968 100644
--- a/security/vbs/kvm_planes.c
+++ b/security/vbs/kvm_planes.c
@@ -23,7 +23,11 @@
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/kvm_para.h>
+#include <asm/sections.h>
#include <asm/kvm_para.h>
+#include <asm/processor.h>
+
+#include "heki.h"
/* ── shared-memory calling area (modelled after the SVSM CAA) ─────── */
@@ -122,7 +126,20 @@ static int kvm_planes_protect_memory(unsigned long pfn,
static int kvm_planes_seal_kernel(void)
{
- return kvm_planes_vtl_call(VBS_CALL_SEAL_KERNEL, NULL, 0, NULL, 0);
+ struct vbs_seal_kernel_req req = {
+ .text_gpa = __pa_symbol(_stext),
+ .text_size = PAGE_ALIGN((u64)(_etext - _stext)),
+ .rodata_gpa = __pa_symbol(__start_rodata),
+ .rodata_size = PAGE_ALIGN((u64)(__end_rodata - __start_rodata)),
+ .cr3 = read_cr3_pa(),
+ };
+
+ pr_info("vbs-kvm: seal_kernel text=[0x%llx+0x%llx] rodata=[0x%llx+0x%llx] cr3=0x%llx\n",
+ req.text_gpa, req.text_size,
+ req.rodata_gpa, req.rodata_size, req.cr3);
+
+ return kvm_planes_vtl_call(VBS_CALL_SEAL_KERNEL,
+ &req, sizeof(req), NULL, 0);
}
/* ── module authentication ────────────────────────────────────────────── */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 17/42] vbs: Add module authentication via VBS/HEKI
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (15 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 16/42] vbs: Add HEKI kernel sealing and fix KVM plane memory attribute guards Sriram Nambakam
@ 2026-08-05 11:02 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 18/42] vbs: Add kexec validation and make module auth non-fatal Sriram Nambakam
` (24 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:02 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Hook the kernel module loader to send module validation requests to the
secure kernel (plane-1 / QEMU) before allowing modules to load, and to
set per-section EPT permissions after module formation.
kernel/module/main.c:
- After add_unformed_module(): call vbs_validate_module() with the
module ELF blob GPA and the kernel's own sig_ok result from
module_sig_check(). If the secure side rejects, loading is aborted.
- After complete_formation(): call vbs_set_module_perms() to apply
EPT permissions per section (text=R+X, rodata=R, data=R+W).
Failure is non-fatal to avoid breaking module loading on ioctl
errors.
- In free_module(): call vbs_unload_module() so the secure side can
release EPT overrides for the freed module.
- All hooks are guarded by vbs_available() and are no-ops when VBS
is not active.
security/vbs/heki.h:
- Add vbs_validate_module_req with module name, ELF GPA/size, and
sig_ok flag (kernel's signature verification result).
- Add vbs_module_section and vbs_set_module_perms_req for per-section
GPA + permissions.
- Add vbs_unload_module_req for module unload notification.
security/vbs/kvm_planes.c:
- Implement kvm_planes_validate_module(): converts vmalloc ELF
pointer to GPA, sends sig_ok flag via VBS_CALL_VALIDATE_MODULE.
- Implement kvm_planes_set_module_perms(): iterates mod->mem[]
array, maps each section type to VBS_MEM_* permissions (TEXT→R+X,
RODATA→R, DATA→R+W), sends via VBS_CALL_SET_MODULE_PERMS.
- Implement kvm_planes_unload_module(): sends module name via
VBS_CALL_UNLOAD_MODULE.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
kernel/module/main.c | 37 ++++++++++++++
security/vbs/heki.h | 46 +++++++++++++++++
security/vbs/kvm_planes.c | 102 +++++++++++++++++++++++++++++++++++---
3 files changed, 178 insertions(+), 7 deletions(-)
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..2d0232fccf18 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -39,6 +39,7 @@
#include <linux/string.h>
#include <linux/mutex.h>
#include <linux/rculist.h>
+#include <linux/vbs.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
#include <linux/set_memory.h>
@@ -1418,6 +1419,11 @@ static void free_module(struct module *mod)
{
trace_module_free(mod);
+ /* Notify the secure kernel that this module is being unloaded
+ * so it can release any EPT permission overrides. */
+ if (vbs_available())
+ vbs_unload_module(mod);
+
codetag_unload_module(mod);
mod_sysfs_teardown(mod);
@@ -3472,6 +3478,22 @@ static int load_module(struct load_info *info, const char __user *uargs,
if (err)
goto free_module;
+ /*
+ * If VBS is available, ask the secure kernel (plane-1) to
+ * validate this module. We pass the module name and the
+ * sig_ok flag from the kernel's own signature check.
+ * Plane-1 can enforce additional policy (e.g., allowlist).
+ */
+ if (vbs_available()) {
+ err = vbs_validate_module(info->hdr, info->len,
+ NULL, info->sig_ok ? 1 : 0);
+ if (err) {
+ pr_warn("vbs: module '%s' rejected by secure kernel (%ld)\n",
+ mod->name, err);
+ goto unlink_mod;
+ }
+ }
+
/*
* We are tainting your kernel if your module gets into
* the modules linked list somehow.
@@ -3539,6 +3561,21 @@ static int load_module(struct load_info *info, const char __user *uargs,
if (err)
goto ddebug_cleanup;
+ /*
+ * If VBS is available, send the module's per-section layout
+ * to the secure kernel so it can enforce EPT permissions:
+ * text → R+X (NO_WRITE), rodata → R (NO_WRITE|NO_EXEC),
+ * data → R+W (no restrictions).
+ */
+ if (vbs_available()) {
+ err = vbs_set_module_perms(mod);
+ if (err)
+ pr_warn("vbs: set_module_perms for %s failed (%ld)\n",
+ mod->name, err);
+ /* Non-fatal: continue loading even if protection fails */
+ err = 0;
+ }
+
err = prepare_coming_module(mod);
if (err)
goto bug_cleanup;
diff --git a/security/vbs/heki.h b/security/vbs/heki.h
index fee986de351a..5b7fa92bce21 100644
--- a/security/vbs/heki.h
+++ b/security/vbs/heki.h
@@ -36,6 +36,52 @@ struct vbs_seal_kernel_req {
__u64 cr3; /* plane-0 kernel CR3 for verification */
} __packed;
+/* ── Module authentication ────────────────────────────────────────────── */
+
+/*
+ * VBS_CALL_VALIDATE_MODULE payload — plane-0 sends the GPA of the module
+ * ELF blob and its appended PKCS#7 signature for plane-1 verification.
+ * The module blob is in guest physical memory; the secure side reads it
+ * directly via the GPA (no copy through the CAA page).
+ */
+struct vbs_validate_module_req {
+ char name[56]; /* module name (null-terminated) */
+ __u64 elf_gpa; /* GPA of the module ELF data */
+ __u64 elf_size; /* size of the ELF data (excl. signature) */
+ __u32 sig_ok; /* 1 if kernel's sig check passed */
+ __u32 reserved; /* padding */
+} __packed;
+
+/*
+ * Per-section descriptor for VBS_CALL_SET_MODULE_PERMS.
+ * Sent as an array in the CAA buffer after the module name.
+ */
+struct vbs_module_section {
+ __u64 gpa; /* section GPA (page-aligned) */
+ __u64 size; /* section size (page-aligned) */
+ __u32 perms; /* VBS_MEM_* permission flags */
+ __u32 type; /* enum mod_mem_type */
+} __packed;
+
+/*
+ * VBS_CALL_SET_MODULE_PERMS payload — after relocation, plane-0 sends
+ * the per-section layout so plane-1 can set EPT permissions.
+ * Sections follow immediately after this header in the buffer.
+ */
+struct vbs_set_module_perms_req {
+ char name[56]; /* module name (null-terminated) */
+ __u32 nr_sections; /* number of vbs_module_section entries */
+ __u32 flags; /* reserved, must be 0 */
+ /* struct vbs_module_section sections[]; follows in buffer */
+} __packed;
+
+/*
+ * VBS_CALL_UNLOAD_MODULE payload — module is being freed.
+ */
+struct vbs_unload_module_req {
+ char name[56]; /* module name (null-terminated) */
+} __packed;
+
/* ── x86-64 page table walker (for plane-1 auditing) ─────────────────── */
/* Classification of a guest-physical page based on page table walk */
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
index 293c960c0968..1114adfbd46c 100644
--- a/security/vbs/kvm_planes.c
+++ b/security/vbs/kvm_planes.c
@@ -23,6 +23,9 @@
#include <linux/mm.h>
#include <linux/io.h>
#include <linux/kvm_para.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/elf.h>
#include <asm/sections.h>
#include <asm/kvm_para.h>
#include <asm/processor.h>
@@ -147,26 +150,111 @@ static int kvm_planes_seal_kernel(void)
static int kvm_planes_validate_module(const void *elf, size_t elf_size,
const void *sig, size_t sig_size)
{
+ struct vbs_validate_module_req req = {};
+ struct page *elf_page;
+ const Elf64_Ehdr *ehdr;
+
+ if (!elf || !elf_size)
+ return -EINVAL;
+
/*
- * Module blobs can be large — for the KVM planes backend we pass
- * the physical address and size to plane-1 via the VTL call and
- * let plane-1 map/read the pages directly from its EPT view.
- * For now, a stub that signals "not yet implemented".
+ * sig_size is repurposed: 1 = kernel's own sig check passed,
+ * 0 = module is unsigned or sig check failed.
*/
+ req.sig_ok = sig_size ? 1 : 0;
+
+ /* Try to extract the module name from the ELF .modinfo section.
+ * For now, just use a placeholder — the name is available at
+ * the call site in load_module() but not passed through the
+ * vbs_ops interface which takes (elf, elf_size, sig, sig_size).
+ */
+ ehdr = elf;
+ if (elf_size >= sizeof(*ehdr) && ehdr->e_ident[0] == 0x7f)
+ strscpy(req.name, "module", sizeof(req.name));
+ else
+ strscpy(req.name, "unknown", sizeof(req.name));
+
+ /* Get GPA of the ELF blob */
+ elf_page = vmalloc_to_page(elf);
+ if (elf_page) {
+ req.elf_gpa = page_to_phys(elf_page) +
+ offset_in_page(elf);
+ req.elf_size = elf_size;
+ }
+
+ pr_debug("vbs-kvm: validate_module elf_gpa=0x%llx size=0x%llx sig_ok=%u\n",
+ req.elf_gpa, req.elf_size, req.sig_ok);
+
return kvm_planes_vtl_call(VBS_CALL_VALIDATE_MODULE,
- NULL, 0, NULL, 0);
+ &req, sizeof(req), NULL, 0);
}
static int kvm_planes_set_module_perms(const struct module *mod)
{
+ struct {
+ struct vbs_set_module_perms_req hdr;
+ struct vbs_module_section sections[MOD_MEM_NUM_TYPES];
+ } __packed req = {};
+ int i, n = 0;
+
+ strscpy(req.hdr.name, mod->name, sizeof(req.hdr.name));
+
+ for (i = 0; i < MOD_MEM_NUM_TYPES; i++) {
+ const struct module_memory *mem = &mod->mem[i];
+ struct vbs_module_section *sec;
+ unsigned long gpa;
+ struct page *p;
+
+ if (!mem->base || !mem->size)
+ continue;
+
+ p = vmalloc_to_page(mem->base);
+ if (!p)
+ continue;
+
+ gpa = page_to_phys(p) + offset_in_page(mem->base);
+ sec = &req.sections[n];
+ sec->gpa = gpa;
+ sec->size = PAGE_ALIGN(mem->size);
+ sec->type = i;
+
+ /* Set permissions based on section type */
+ switch (i) {
+ case MOD_TEXT:
+ case MOD_INIT_TEXT:
+ sec->perms = VBS_MEM_READ | VBS_MEM_EXEC;
+ break;
+ case MOD_RODATA:
+ case MOD_RO_AFTER_INIT:
+ case MOD_INIT_RODATA:
+ sec->perms = VBS_MEM_READ;
+ break;
+ default: /* MOD_DATA, MOD_INIT_DATA */
+ sec->perms = VBS_MEM_READ | VBS_MEM_WRITE;
+ break;
+ }
+ n++;
+ }
+
+ req.hdr.nr_sections = n;
+
+ pr_debug("vbs-kvm: set_module_perms %s: %d sections\n",
+ mod->name, n);
+
return kvm_planes_vtl_call(VBS_CALL_SET_MODULE_PERMS,
- NULL, 0, NULL, 0);
+ &req,
+ sizeof(req.hdr) + n * sizeof(req.sections[0]),
+ NULL, 0);
}
static int kvm_planes_unload_module(const struct module *mod)
{
+ struct vbs_unload_module_req req = {};
+
+ strscpy(req.name, mod->name, sizeof(req.name));
+
return kvm_planes_vtl_call(VBS_CALL_UNLOAD_MODULE,
- NULL, 0, NULL, 0);
+ &req, sizeof(req), NULL, 0);
}
/* ── key / certificate management ─────────────────────────────────────── */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 18/42] vbs: Add kexec validation and make module auth non-fatal
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (16 preceding siblings ...)
2026-08-05 11:02 ` [RFC PATCH v1 17/42] vbs: Add module authentication via VBS/HEKI Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 19/42] Merge branch 'master' into vm-planes Sriram Nambakam
` (23 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add VBS/HEKI kexec validation hooks so the secure kernel (plane-1) can
approve or reject kexec kernel images before they are loaded.
kexec_file.c:
- After signature verification passes, call vbs_kexec_validate() to
send the kernel image GPA, size, and sig_ok flag to the secure
kernel via the VTL call interface.
- If the secure kernel rejects the image, kexec_file_load fails.
kexec_core.c:
- In kimage_free(), call vbs_kexec_invalidate() to notify the secure
kernel that a previously validated kexec image is being freed.
security/vbs/heki.h:
- Add struct vbs_kexec_validate_req (kernel_gpa, kernel_size,
sig_ok, flags).
security/vbs/kvm_planes.c:
- Implement kvm_planes_kexec_validate(): translates the vmalloc
kernel buffer to a GPA, populates the request, and issues the
VTL call to plane-1.
- Implement kvm_planes_kexec_invalidate(): issues the VTL call
with no payload.
kernel/module/main.c:
- Change VBS module validation from fatal to non-fatal. If the
secure kernel rejects a module, log a warning but allow loading
to continue. This prevents unsigned modules (common at boot)
from blocking the system. A strict policy can be enforced later.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
kernel/kexec_core.c | 5 +++++
kernel/kexec_file.c | 20 ++++++++++++++++++++
kernel/module/main.c | 9 +++++----
security/vbs/heki.h | 14 ++++++++++++++
security/vbs/kvm_planes.c | 24 +++++++++++++++++++++++-
5 files changed, 67 insertions(+), 5 deletions(-)
diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c
index dc770b9a6d05..a7bdbfaf68c8 100644
--- a/kernel/kexec_core.c
+++ b/kernel/kexec_core.c
@@ -43,6 +43,7 @@
#include <linux/kmsg_dump.h>
#include <linux/dma-map-ops.h>
#include <linux/sysfs.h>
+#include <linux/vbs.h>
#include <asm/page.h>
#include <asm/sections.h>
@@ -580,6 +581,10 @@ void kimage_free(struct kimage *image)
if (!image)
return;
+ /* Notify the secure kernel that a kexec image is being freed */
+ if (vbs_available())
+ vbs_kexec_invalidate();
+
#ifdef CONFIG_CRASH_DUMP
if (image->vmcoreinfo_data_copy) {
crash_update_vmcoreinfo_safecopy(NULL);
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 2bfbb2d144e6..81cf454ab516 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -27,6 +27,7 @@
#include <linux/syscalls.h>
#include <linux/vmalloc.h>
#include <linux/dma-map-ops.h>
+#include <linux/vbs.h>
#include "kexec_internal.h"
#ifdef CONFIG_KEXEC_SIG
@@ -243,6 +244,25 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
if (ret)
goto out;
#endif
+
+ /*
+ * If VBS is available, ask the secure kernel (plane-1) to
+ * validate the kexec kernel image. Pass sig_ok based on
+ * whether CONFIG_KEXEC_SIG is enabled and the check passed.
+ */
+ if (vbs_available()) {
+ int sig_ok = 0;
+#ifdef CONFIG_KEXEC_SIG
+ sig_ok = 1; /* we got here, so sig check passed */
+#endif
+ ret = vbs_kexec_validate(image->kernel_buf,
+ image->kernel_buf_len,
+ NULL, sig_ok);
+ if (ret) {
+ pr_warn("vbs: kexec kernel rejected by secure kernel (%d)\n", ret);
+ goto out;
+ }
+ }
/* It is possible that there no initramfs is being loaded */
if (!(flags & KEXEC_FILE_NO_INITRAMFS)) {
ret = kernel_read_file_from_fd(initrd_fd, 0, &image->initrd_buf,
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 2d0232fccf18..3b46d6c0fb41 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -3487,11 +3487,12 @@ static int load_module(struct load_info *info, const char __user *uargs,
if (vbs_available()) {
err = vbs_validate_module(info->hdr, info->len,
NULL, info->sig_ok ? 1 : 0);
- if (err) {
- pr_warn("vbs: module '%s' rejected by secure kernel (%ld)\n",
+ if (err)
+ pr_warn("vbs: module '%s' validation returned (%ld) — continuing\n",
mod->name, err);
- goto unlink_mod;
- }
+ /* Non-fatal: allow loading to continue even if VBS rejects.
+ * A strict policy can be enforced later by changing this. */
+ err = 0;
}
/*
diff --git a/security/vbs/heki.h b/security/vbs/heki.h
index 5b7fa92bce21..fb485f171045 100644
--- a/security/vbs/heki.h
+++ b/security/vbs/heki.h
@@ -82,6 +82,20 @@ struct vbs_unload_module_req {
char name[56]; /* module name (null-terminated) */
} __packed;
+/* ── Kexec validation ─────────────────────────────────────────────────── */
+
+/*
+ * VBS_CALL_KEXEC_VALIDATE payload — plane-0 sends the GPA and size of
+ * the kexec kernel image for plane-1 validation before allowing the
+ * kexec to proceed.
+ */
+struct vbs_kexec_validate_req {
+ __u64 kernel_gpa; /* GPA of the kernel image buffer */
+ __u64 kernel_size; /* size of the kernel image */
+ __u32 sig_ok; /* 1 if kernel's sig check passed */
+ __u32 flags; /* reserved, must be 0 */
+} __packed;
+
/* ── x86-64 page table walker (for plane-1 auditing) ─────────────────── */
/* Classification of a guest-physical page based on page table walk */
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
index 1114adfbd46c..061163a4d303 100644
--- a/security/vbs/kvm_planes.c
+++ b/security/vbs/kvm_planes.c
@@ -282,12 +282,34 @@ static int kvm_planes_send_certs(const void *certs, size_t certs_size)
static int kvm_planes_kexec_validate(const void *kernel, size_t kernel_size,
const void *sig, size_t sig_size)
{
+ struct vbs_kexec_validate_req req = {};
+ struct page *page;
+
+ if (!kernel || !kernel_size)
+ return -EINVAL;
+
+ /*
+ * sig_size is repurposed: 1 = kernel's sig check passed,
+ * 0 = unsigned or failed (same pattern as module validation).
+ */
+ req.sig_ok = sig_size ? 1 : 0;
+ req.kernel_size = kernel_size;
+
+ /* Get GPA of the kernel image buffer (first page) */
+ page = vmalloc_to_page(kernel);
+ if (page)
+ req.kernel_gpa = page_to_phys(page) + offset_in_page(kernel);
+
+ pr_info("vbs-kvm: kexec_validate gpa=0x%llx size=0x%llx sig_ok=%u\n",
+ req.kernel_gpa, req.kernel_size, req.sig_ok);
+
return kvm_planes_vtl_call(VBS_CALL_KEXEC_VALIDATE,
- NULL, 0, NULL, 0);
+ &req, sizeof(req), NULL, 0);
}
static int kvm_planes_kexec_invalidate(void)
{
+ pr_info("vbs-kvm: kexec_invalidate\n");
return kvm_planes_vtl_call(VBS_CALL_KEXEC_INVALIDATE,
NULL, 0, NULL, 0);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 19/42] Merge branch 'master' into vm-planes
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (17 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 18/42] vbs: Add kexec validation and make module auth non-fatal Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 20/42] kvm: x86: fix merged plane API/stat build regressions Sriram Nambakam
` (22 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
arch/x86/kvm/svm/svm.c | 2 +-
arch/x86/kvm/x86.c | 2 +-
kernel/kexec_file.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index ce242e86c5ea..bc281fbc54c2 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -3240,7 +3240,7 @@ static int interrupt_window_interception(struct kvm_vcpu *vcpu)
kvm_make_request(KVM_REQ_EVENT, vcpu);
svm_clear_vintr(to_svm(vcpu));
- ++vcpu->stat.irq_window_exits;
+ ++vcpu->stat->irq_window_exits;
return 1;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 4b99016fe536..d4210053e6b8 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -11129,7 +11129,7 @@ void kvm_inc_or_dec_irq_window_inhibit(struct kvm *kvm, bool inc)
*/
guard(rwsem_write)(&kvm->arch.apicv_update_lock);
if (atomic_add_return(add, &kvm->arch.apicv_nr_irq_window_req) == inc)
- __kvm_set_or_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_IRQWIN, inc);
+ __kvm_set_or_clear_apicv_inhibit(kvm->planes[0], APICV_INHIBIT_REASON_IRQWIN, inc);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_inc_or_dec_irq_window_inhibit);
diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c
index 81cf454ab516..da44bac7df74 100644
--- a/kernel/kexec_file.c
+++ b/kernel/kexec_file.c
@@ -259,7 +259,7 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
image->kernel_buf_len,
NULL, sig_ok);
if (ret) {
- pr_warn("vbs: kexec kernel rejected by secure kernel (%d)\n", ret);
+ pr_warn("vbs: kexec kernel rejected by secure kernel (%zd)\n", ret);
goto out;
}
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 20/42] kvm: x86: fix merged plane API/stat build regressions
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (18 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 19/42] Merge branch 'master' into vm-planes Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 21/42] KVM: x86: exit VM planes and VBS hypercalls to userspace Sriram Nambakam
` (21 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
arch/x86/kvm/debugfs.c | 2 +-
arch/x86/kvm/hyperv.c | 8 +-
arch/x86/kvm/kvm_cache_regs.h | 249 ++++++++++++++++++++++++++++++++++
arch/x86/kvm/mmu/mmu.c | 41 +++---
arch/x86/kvm/mmu/spte.h | 10 +-
arch/x86/kvm/mmu/tdp_mmu.c | 2 +-
arch/x86/kvm/svm/avic.c | 2 +-
arch/x86/kvm/svm/sev.c | 4 +-
arch/x86/kvm/vmx/vmx.c | 20 +--
arch/x86/kvm/xen.c | 2 -
include/uapi/linux/kvm.h | 2 +
virt/kvm/guest_memfd.c | 3 +-
12 files changed, 291 insertions(+), 54 deletions(-)
create mode 100644 arch/x86/kvm/kvm_cache_regs.h
diff --git a/arch/x86/kvm/debugfs.c b/arch/x86/kvm/debugfs.c
index 192cc7228197..0074a56e45b4 100644
--- a/arch/x86/kvm/debugfs.c
+++ b/arch/x86/kvm/debugfs.c
@@ -24,7 +24,7 @@ DEFINE_SIMPLE_ATTRIBUTE(vcpu_timer_advance_ns_fops, vcpu_get_timer_advance_ns, N
static int vcpu_get_guest_mode(void *data, u64 *val)
{
struct kvm_vcpu *vcpu = (struct kvm_vcpu *) data;
- *val = vcpu->stat->guest_mode;
+ *val = vcpu->stat.guest_mode;
return 0;
}
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 75d5d7f7994e..ee6b32d2a5cb 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -145,7 +145,7 @@ static void synic_update_vector(struct kvm_vcpu_hv_synic *synic,
* Inhibit APICv if any vCPU is using SynIC's AutoEOI, which relies on
* the hypervisor to manually inject IRQs.
*/
- __kvm_set_or_clear_apicv_inhibit(vcpu_to_plane(vcpu),
+ __kvm_set_or_clear_apicv_inhibit(vcpu->kvm,
APICV_INHIBIT_REASON_HYPERV,
!!hv->synic_auto_eoi_used);
@@ -491,8 +491,6 @@ static int synic_set_irq(struct kvm_vcpu_hv_synic *synic, u32 sint)
irq.delivery_mode = APIC_DM_FIXED;
irq.vector = vector;
irq.level = 1;
- irq.plane = vcpu->plane;
-
ret = kvm_irq_delivery_to_apic(vcpu->plane, vcpu->arch.apic, &irq);
trace_kvm_hv_synic_set_irq(vcpu->vcpu_id, sint, irq.vector, ret);
return ret;
@@ -1999,7 +1997,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu)
kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE);
}
- ++vcpu->stat->tlb_flush;
+ ++vcpu->stat.tlb_flush;
}
return 0;
@@ -2403,7 +2401,7 @@ static int kvm_hv_hypercall_complete(struct kvm_vcpu *vcpu, u64 result)
trace_kvm_hv_hypercall_done(result);
kvm_hv_hypercall_set_result(vcpu, result);
- ++vcpu->stat->hypercalls;
+ ++vcpu->stat.hypercalls;
ret = kvm_skip_emulated_instruction(vcpu);
diff --git a/arch/x86/kvm/kvm_cache_regs.h b/arch/x86/kvm/kvm_cache_regs.h
new file mode 100644
index 000000000000..8ddb01191d6f
--- /dev/null
+++ b/arch/x86/kvm/kvm_cache_regs.h
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef ASM_KVM_CACHE_REGS_H
+#define ASM_KVM_CACHE_REGS_H
+
+#include <linux/kvm_host.h>
+
+#define KVM_POSSIBLE_CR0_GUEST_BITS (X86_CR0_TS | X86_CR0_WP)
+#define KVM_POSSIBLE_CR4_GUEST_BITS \
+ (X86_CR4_PVI | X86_CR4_DE | X86_CR4_PCE | X86_CR4_OSFXSR \
+ | X86_CR4_OSXMMEXCPT | X86_CR4_PGE | X86_CR4_TSD | X86_CR4_FSGSBASE \
+ | X86_CR4_CET)
+
+#define X86_CR0_PDPTR_BITS (X86_CR0_CD | X86_CR0_NW | X86_CR0_PG)
+#define X86_CR4_TLBFLUSH_BITS (X86_CR4_PGE | X86_CR4_PCIDE | X86_CR4_PAE | X86_CR4_SMEP)
+#define X86_CR4_PDPTR_BITS (X86_CR4_PGE | X86_CR4_PSE | X86_CR4_PAE | X86_CR4_SMEP)
+
+static_assert(!(KVM_POSSIBLE_CR0_GUEST_BITS & X86_CR0_PDPTR_BITS));
+
+#define BUILD_KVM_GPR_ACCESSORS(lname, uname) \
+static __always_inline unsigned long kvm_##lname##_read(struct kvm_vcpu *vcpu)\
+{ \
+ return vcpu->arch.regs[VCPU_REGS_##uname]; \
+} \
+static __always_inline void kvm_##lname##_write(struct kvm_vcpu *vcpu, \
+ unsigned long val) \
+{ \
+ vcpu->arch.regs[VCPU_REGS_##uname] = val; \
+}
+BUILD_KVM_GPR_ACCESSORS(rax, RAX)
+BUILD_KVM_GPR_ACCESSORS(rbx, RBX)
+BUILD_KVM_GPR_ACCESSORS(rcx, RCX)
+BUILD_KVM_GPR_ACCESSORS(rdx, RDX)
+BUILD_KVM_GPR_ACCESSORS(rbp, RBP)
+BUILD_KVM_GPR_ACCESSORS(rsi, RSI)
+BUILD_KVM_GPR_ACCESSORS(rdi, RDI)
+#ifdef CONFIG_X86_64
+BUILD_KVM_GPR_ACCESSORS(r8, R8)
+BUILD_KVM_GPR_ACCESSORS(r9, R9)
+BUILD_KVM_GPR_ACCESSORS(r10, R10)
+BUILD_KVM_GPR_ACCESSORS(r11, R11)
+BUILD_KVM_GPR_ACCESSORS(r12, R12)
+BUILD_KVM_GPR_ACCESSORS(r13, R13)
+BUILD_KVM_GPR_ACCESSORS(r14, R14)
+BUILD_KVM_GPR_ACCESSORS(r15, R15)
+#endif
+
+/*
+ * Using the register cache from interrupt context is generally not allowed, as
+ * caching a register and marking it available/dirty can't be done atomically,
+ * i.e. accesses from interrupt context may clobber state or read stale data if
+ * the vCPU task is in the process of updating the cache. The exception is if
+ * KVM is handling a PMI IRQ/NMI VM-Exit, as that bound code sequence doesn't
+ * touch the cache, it runs after the cache is reset (post VM-Exit), and PMIs
+ * need to access several registers that are cacheable.
+ */
+#define kvm_assert_register_caching_allowed(vcpu) \
+ lockdep_assert_once(in_task() || kvm_arch_pmi_in_guest(vcpu))
+
+/*
+ * avail dirty
+ * 0 0 register in VMCS/VMCB
+ * 0 1 *INVALID*
+ * 1 0 register in vcpu->arch
+ * 1 1 register in vcpu->arch, needs to be stored back
+ */
+static inline bool kvm_register_is_available(struct kvm_vcpu *vcpu,
+ enum kvm_reg reg)
+{
+ kvm_assert_register_caching_allowed(vcpu);
+ return test_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
+}
+
+static inline bool kvm_register_is_dirty(struct kvm_vcpu *vcpu,
+ enum kvm_reg reg)
+{
+ kvm_assert_register_caching_allowed(vcpu);
+ return test_bit(reg, (unsigned long *)&vcpu->arch.regs_dirty);
+}
+
+static inline void kvm_register_mark_available(struct kvm_vcpu *vcpu,
+ enum kvm_reg reg)
+{
+ kvm_assert_register_caching_allowed(vcpu);
+ __set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
+}
+
+static inline void kvm_register_mark_dirty(struct kvm_vcpu *vcpu,
+ enum kvm_reg reg)
+{
+ kvm_assert_register_caching_allowed(vcpu);
+ __set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
+ __set_bit(reg, (unsigned long *)&vcpu->arch.regs_dirty);
+}
+
+/*
+ * kvm_register_test_and_mark_available() is a special snowflake that uses an
+ * arch bitop directly to avoid the explicit instrumentation that comes with
+ * the generic bitops. This allows code that cannot be instrumented (noinstr
+ * functions), e.g. the low level VM-Enter/VM-Exit paths, to cache registers.
+ */
+static __always_inline bool kvm_register_test_and_mark_available(struct kvm_vcpu *vcpu,
+ enum kvm_reg reg)
+{
+ kvm_assert_register_caching_allowed(vcpu);
+ return arch___test_and_set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
+}
+
+/*
+ * The "raw" register helpers are only for cases where the full 64 bits of a
+ * register are read/written irrespective of current vCPU mode. In other words,
+ * odds are good you shouldn't be using the raw variants.
+ */
+static inline unsigned long kvm_register_read_raw(struct kvm_vcpu *vcpu, int reg)
+{
+ if (WARN_ON_ONCE((unsigned int)reg >= NR_VCPU_REGS))
+ return 0;
+
+ if (!kvm_register_is_available(vcpu, reg))
+ kvm_x86_call(cache_reg)(vcpu, reg);
+
+ return vcpu->arch.regs[reg];
+}
+
+static inline void kvm_register_write_raw(struct kvm_vcpu *vcpu, int reg,
+ unsigned long val)
+{
+ if (WARN_ON_ONCE((unsigned int)reg >= NR_VCPU_REGS))
+ return;
+
+ vcpu->arch.regs[reg] = val;
+ kvm_register_mark_dirty(vcpu, reg);
+}
+
+static inline unsigned long kvm_rip_read(struct kvm_vcpu *vcpu)
+{
+ return kvm_register_read_raw(vcpu, VCPU_REGS_RIP);
+}
+
+static inline void kvm_rip_write(struct kvm_vcpu *vcpu, unsigned long val)
+{
+ kvm_register_write_raw(vcpu, VCPU_REGS_RIP, val);
+}
+
+static inline unsigned long kvm_rsp_read(struct kvm_vcpu *vcpu)
+{
+ return kvm_register_read_raw(vcpu, VCPU_REGS_RSP);
+}
+
+static inline void kvm_rsp_write(struct kvm_vcpu *vcpu, unsigned long val)
+{
+ kvm_register_write_raw(vcpu, VCPU_REGS_RSP, val);
+}
+
+static inline u64 kvm_pdptr_read(struct kvm_vcpu *vcpu, int index)
+{
+ might_sleep(); /* on svm */
+
+ if (!kvm_register_is_available(vcpu, VCPU_EXREG_PDPTR))
+ kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_PDPTR);
+
+ return vcpu->arch.walk_mmu->pdptrs[index];
+}
+
+static inline void kvm_pdptr_write(struct kvm_vcpu *vcpu, int index, u64 value)
+{
+ vcpu->arch.walk_mmu->pdptrs[index] = value;
+}
+
+static inline ulong kvm_read_cr0_bits(struct kvm_vcpu *vcpu, ulong mask)
+{
+ ulong tmask = mask & KVM_POSSIBLE_CR0_GUEST_BITS;
+ if ((tmask & vcpu->arch.cr0_guest_owned_bits) &&
+ !kvm_register_is_available(vcpu, VCPU_EXREG_CR0))
+ kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_CR0);
+ return vcpu->arch.cr0 & mask;
+}
+
+static __always_inline bool kvm_is_cr0_bit_set(struct kvm_vcpu *vcpu,
+ unsigned long cr0_bit)
+{
+ BUILD_BUG_ON(!is_power_of_2(cr0_bit));
+
+ return !!kvm_read_cr0_bits(vcpu, cr0_bit);
+}
+
+static inline ulong kvm_read_cr0(struct kvm_vcpu *vcpu)
+{
+ return kvm_read_cr0_bits(vcpu, ~0UL);
+}
+
+static inline ulong kvm_read_cr4_bits(struct kvm_vcpu *vcpu, ulong mask)
+{
+ ulong tmask = mask & KVM_POSSIBLE_CR4_GUEST_BITS;
+ if ((tmask & vcpu->arch.cr4_guest_owned_bits) &&
+ !kvm_register_is_available(vcpu, VCPU_EXREG_CR4))
+ kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_CR4);
+ return vcpu->arch.cr4 & mask;
+}
+
+static __always_inline bool kvm_is_cr4_bit_set(struct kvm_vcpu *vcpu,
+ unsigned long cr4_bit)
+{
+ BUILD_BUG_ON(!is_power_of_2(cr4_bit));
+
+ return !!kvm_read_cr4_bits(vcpu, cr4_bit);
+}
+
+static inline ulong kvm_read_cr3(struct kvm_vcpu *vcpu)
+{
+ if (!kvm_register_is_available(vcpu, VCPU_EXREG_CR3))
+ kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_CR3);
+ return vcpu->arch.cr3;
+}
+
+static inline ulong kvm_read_cr4(struct kvm_vcpu *vcpu)
+{
+ return kvm_read_cr4_bits(vcpu, ~0UL);
+}
+
+static inline u64 kvm_read_edx_eax(struct kvm_vcpu *vcpu)
+{
+ return (kvm_rax_read(vcpu) & -1u)
+ | ((u64)(kvm_rdx_read(vcpu) & -1u) << 32);
+}
+
+static inline void enter_guest_mode(struct kvm_vcpu *vcpu)
+{
+ vcpu->arch.hflags |= HF_GUEST_MASK;
+ vcpu->stat.guest_mode = 1;
+}
+
+static inline void leave_guest_mode(struct kvm_vcpu *vcpu)
+{
+ vcpu->arch.hflags &= ~HF_GUEST_MASK;
+
+ if (vcpu->arch.load_eoi_exitmap_pending) {
+ vcpu->arch.load_eoi_exitmap_pending = false;
+ kvm_make_request(KVM_REQ_LOAD_EOI_EXITMAP, vcpu);
+ }
+
+ vcpu->stat.guest_mode = 0;
+}
+
+static inline bool is_guest_mode(struct kvm_vcpu *vcpu)
+{
+ return vcpu->arch.hflags & HF_GUEST_MASK;
+}
+
+#endif
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 3b861a42a712..6e41c5df72ed 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -3100,7 +3100,7 @@ static int mmu_set_spte(struct kvm_vcpu *vcpu, struct kvm_memory_slot *slot,
}
if (unlikely(is_noslot_pfn(pfn))) {
- vcpu->stat->pf_mmio_spte_created++;
+ vcpu->stat.pf_mmio_spte_created++;
mark_mmio_spte(vcpu, sptep, gfn, pte_access);
if (flush)
kvm_flush_remote_tlbs_gfn(vcpu->kvm, gfn, level);
@@ -3809,7 +3809,7 @@ static int fast_page_fault(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault)
walk_shadow_page_lockless_end(vcpu);
if (ret != RET_PF_INVALID)
- vcpu->stat->pf_fast++;
+ vcpu->stat.pf_fast++;
return ret;
}
@@ -4602,7 +4602,7 @@ void kvm_arch_async_page_ready(struct kvm_vcpu *vcpu, struct kvm_async_pf *work)
* truly spurious and never trigger emulation
*/
if (r == RET_PF_FIXED)
- vcpu->stat->pf_fixed++;
+ vcpu->stat.pf_fixed++;
}
static void kvm_mmu_finish_page_fault(struct kvm_vcpu *vcpu,
@@ -6529,7 +6529,7 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
}
if (r == RET_PF_INVALID) {
- vcpu->stat->pf_taken++;
+ vcpu->stat.pf_taken++;
r = kvm_mmu_do_page_fault(vcpu, cr2_or_gpa, error_code, false,
&emulation_type, NULL);
@@ -6545,11 +6545,11 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
&emulation_type);
if (r == RET_PF_FIXED)
- vcpu->stat->pf_fixed++;
+ vcpu->stat.pf_fixed++;
else if (r == RET_PF_EMULATE)
- vcpu->stat->pf_emulate++;
+ vcpu->stat.pf_emulate++;
else if (r == RET_PF_SPURIOUS)
- vcpu->stat->pf_spurious++;
+ vcpu->stat.pf_spurious++;
/*
* None of handle_mmio_page_fault(), kvm_mmu_do_page_fault(), or
@@ -6663,7 +6663,7 @@ void kvm_mmu_invlpg(struct kvm_vcpu *vcpu, gva_t gva)
* done here for them.
*/
kvm_mmu_invalidate_addr(vcpu, vcpu->arch.walk_mmu, gva, KVM_MMU_ROOTS_ALL);
- ++vcpu->stat->invlpg;
+ ++vcpu->stat.invlpg;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_mmu_invlpg);
@@ -6685,7 +6685,7 @@ void kvm_mmu_invpcid_gva(struct kvm_vcpu *vcpu, gva_t gva, unsigned long pcid)
if (roots)
kvm_mmu_invalidate_addr(vcpu, mmu, gva, roots);
- ++vcpu->stat->invlpg;
+ ++vcpu->stat.invlpg;
/*
* Mappings not reachable via the current cr3 or the prev_roots will be
@@ -8024,14 +8024,12 @@ static void hugepage_set_mixed(struct kvm_memory_slot *slot, gfn_t gfn,
lpage_info_slot(gfn, slot, level)->disallow_lpage |= KVM_LPAGE_MIXED_FLAG;
}
-bool kvm_arch_pre_set_memory_attributes(struct kvm_plane *plane,
+bool kvm_arch_pre_set_memory_attributes(struct kvm *kvm,
struct kvm_gfn_range *range)
{
struct kvm_memory_slot *slot = range->slot;
int level;
- struct kvm *kvm = plane->kvm;
-
/*
* Zap SPTEs even if the slot can't be mapped PRIVATE. KVM x86 only
* supports KVM_MEMORY_ATTRIBUTE_PRIVATE, and so it *seems* like KVM
@@ -8087,27 +8085,26 @@ bool kvm_arch_pre_set_memory_attributes(struct kvm_plane *plane,
return kvm_unmap_gfn_range(kvm, range);
}
-static bool hugepage_has_attrs(struct kvm_plane *plane, struct kvm_memory_slot *slot,
+static bool hugepage_has_attrs(struct kvm *kvm, struct kvm_memory_slot *slot,
gfn_t gfn, int level, unsigned long attrs)
{
const unsigned long start = gfn;
const unsigned long end = start + KVM_PAGES_PER_HPAGE(level);
if (level == PG_LEVEL_2M)
- return kvm_range_has_memory_attributes(plane, start, end, ~0, attrs);
+ return kvm_range_has_memory_attributes(kvm, start, end, ~0, attrs);
for (gfn = start; gfn < end; gfn += KVM_PAGES_PER_HPAGE(level - 1)) {
if (hugepage_test_mixed(slot, gfn, level - 1) ||
- attrs != kvm_get_plane_memory_attributes(plane, gfn))
+ attrs != kvm_get_memory_attributes(kvm, gfn))
return false;
}
return true;
}
-bool kvm_arch_post_set_memory_attributes(struct kvm_plane *plane,
+bool kvm_arch_post_set_memory_attributes(struct kvm *kvm,
struct kvm_gfn_range *range)
{
- struct kvm *kvm = plane->kvm;
unsigned long attrs = range->arg.attributes;
struct kvm_memory_slot *slot = range->slot;
int level;
@@ -8141,7 +8138,7 @@ bool kvm_arch_post_set_memory_attributes(struct kvm_plane *plane,
*/
if (gfn >= slot->base_gfn &&
gfn + nr_pages <= slot->base_gfn + slot->npages) {
- if (hugepage_has_attrs(plane, slot, gfn, level, attrs))
+ if (hugepage_has_attrs(kvm, slot, gfn, level, attrs))
hugepage_clear_mixed(slot, gfn, level);
else
hugepage_set_mixed(slot, gfn, level);
@@ -8163,7 +8160,7 @@ bool kvm_arch_post_set_memory_attributes(struct kvm_plane *plane,
*/
if (gfn < range->end &&
(gfn + nr_pages) <= (slot->base_gfn + slot->npages)) {
- if (hugepage_has_attrs(plane, slot, gfn, level, attrs))
+ if (hugepage_has_attrs(kvm, slot, gfn, level, attrs))
hugepage_clear_mixed(slot, gfn, level);
else
hugepage_set_mixed(slot, gfn, level);
@@ -8175,13 +8172,11 @@ bool kvm_arch_post_set_memory_attributes(struct kvm_plane *plane,
void kvm_mmu_init_memslot_memory_attributes(struct kvm *kvm,
struct kvm_memory_slot *slot)
{
- struct kvm_plane *plane0;
int level;
if (!kvm_arch_has_private_mem(kvm))
return;
- plane0 = kvm->planes[0];
for (level = PG_LEVEL_2M; level <= KVM_MAX_HUGEPAGE_LEVEL; level++) {
/*
* Don't bother tracking mixed attributes for pages that can't
@@ -8201,9 +8196,9 @@ void kvm_mmu_init_memslot_memory_attributes(struct kvm *kvm,
* be manually checked as the attributes may already be mixed.
*/
for (gfn = start; gfn < end; gfn += nr_pages) {
- unsigned long attrs = kvm_get_plane_memory_attributes(plane0, gfn);
+ unsigned long attrs = kvm_get_memory_attributes(kvm, gfn);
- if (hugepage_has_attrs(plane0, slot, gfn, level, attrs))
+ if (hugepage_has_attrs(kvm, slot, gfn, level, attrs))
hugepage_clear_mixed(slot, gfn, level);
else
hugepage_set_mixed(slot, gfn, level);
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 421836fd3932..144f7c5a1040 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -580,8 +580,8 @@ void __init kvm_mmu_spte_module_init(void);
void kvm_mmu_reset_all_pte_masks(void);
/*
- * Apply per-plane memory protection attributes to pte_access.
- * If the plane's mem_attr_array has NO_WRITE or NO_EXEC set for a GFN,
+ * Apply memory protection attributes to pte_access.
+ * If memory attributes have NO_WRITE or NO_EXEC set for a GFN,
* strip the corresponding access bits before building the SPTE.
*/
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
@@ -589,13 +589,9 @@ static inline unsigned int kvm_plane_filter_pte_access(struct kvm_vcpu *vcpu,
gfn_t gfn,
unsigned int pte_access)
{
- struct kvm_plane *plane = vcpu_to_plane(vcpu);
unsigned long attrs;
- if (!plane)
- return pte_access;
-
- attrs = kvm_get_plane_memory_attributes(plane, gfn);
+ attrs = kvm_get_memory_attributes(vcpu->kvm, gfn);
if (attrs & KVM_MEMORY_ATTRIBUTE_NO_WRITE)
pte_access &= ~ACC_WRITE_MASK;
if (attrs & KVM_MEMORY_ATTRIBUTE_NO_EXEC)
diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
index 0603445377aa..83bee43a3f67 100644
--- a/arch/x86/kvm/mmu/tdp_mmu.c
+++ b/arch/x86/kvm/mmu/tdp_mmu.c
@@ -1165,7 +1165,7 @@ static int tdp_mmu_map_handle_target_level(struct kvm_vcpu *vcpu,
/* If a MMIO SPTE is installed, the MMIO will need to be emulated. */
if (unlikely(is_mmio_spte(vcpu->kvm, new_spte))) {
- vcpu->stat->pf_mmio_spte_created++;
+ vcpu->stat.pf_mmio_spte_created++;
trace_mark_mmio_spte(rcu_dereference(iter->sptep), iter->gfn,
new_spte);
ret = RET_PF_EMULATE;
diff --git a/arch/x86/kvm/svm/avic.c b/arch/x86/kvm/svm/avic.c
index 251e36f5f0f7..58e493a80cb0 100644
--- a/arch/x86/kvm/svm/avic.c
+++ b/arch/x86/kvm/svm/avic.c
@@ -404,7 +404,7 @@ static int avic_init_backing_page(struct kvm_vcpu *vcpu)
* fully initialized AVIC.
*/
if (id > max_id) {
- kvm_set_apicv_inhibit(vcpu->kvm->planes[0], APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG);
+ kvm_set_apicv_inhibit(vcpu->kvm, APICV_INHIBIT_REASON_PHYSICAL_ID_TOO_BIG);
vcpu->arch.apic->apicv_active = false;
return 0;
}
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 79fee7ebc19b..53e76d22eb08 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -569,7 +569,7 @@ static int __sev_guest_init(struct kvm *kvm, struct kvm_sev_cmd *argp,
INIT_LIST_HEAD(&sev->mirror_vms);
sev->need_init = false;
- kvm_set_apicv_inhibit(kvm->planes[0], APICV_INHIBIT_REASON_SEV);
+ kvm_set_apicv_inhibit(kvm, APICV_INHIBIT_REASON_SEV);
return 0;
@@ -4832,7 +4832,7 @@ int sev_handle_vmgexit(struct kvm_vcpu *vcpu)
svm->sev_es.ghcb_sa);
}
case SVM_VMGEXIT_NMI_COMPLETE:
- ++vcpu->stat->nmi_window_exits;
+ ++vcpu->stat.nmi_window_exits;
svm->nmi_masked = false;
kvm_make_request(KVM_REQ_EVENT, vcpu);
return 1;
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index ee1d606e3314..cdb320169f5c 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -417,7 +417,7 @@ static noinstr void vmx_l1d_flush(struct kvm_vcpu *vcpu)
kvm_clear_cpu_l1tf_flush_l1d();
}
- vcpu->stat->l1d_flush++;
+ vcpu->stat.l1d_flush++;
if (static_cpu_has(X86_FEATURE_FLUSH_L1D)) {
native_wrmsrq(MSR_IA32_FLUSH_CMD, L1D_FLUSH);
@@ -1399,7 +1399,7 @@ static void vmx_prepare_switch_to_host(struct vcpu_vmx *vmx)
host_state = &vmx->loaded_vmcs->host_state;
- ++vmx->vcpu.stat->host_state_reload;
+ ++vmx->vcpu.stat.host_state_reload;
#ifdef CONFIG_X86_64
rdmsrq(MSR_KERNEL_GS_BASE, vmx->msr_guest_kernel_gs_base);
@@ -5111,7 +5111,7 @@ void vmx_inject_irq(struct kvm_vcpu *vcpu, bool reinjected)
trace_kvm_inj_virq(irq, vcpu->arch.interrupt.soft, reinjected);
- ++vcpu->stat->irq_injections;
+ ++vcpu->stat.irq_injections;
if (vmx->rmode.vm86_active) {
int inc_eip = 0;
if (vcpu->arch.interrupt.soft)
@@ -5148,7 +5148,7 @@ void vmx_inject_nmi(struct kvm_vcpu *vcpu)
vmx->loaded_vmcs->vnmi_blocked_time = 0;
}
- ++vcpu->stat->nmi_injections;
+ ++vcpu->stat.nmi_injections;
vmx->loaded_vmcs->nmi_known_unmasked = false;
if (vmx->rmode.vm86_active) {
@@ -5560,7 +5560,7 @@ static int handle_exception_nmi(struct kvm_vcpu *vcpu)
static __always_inline int handle_external_interrupt(struct kvm_vcpu *vcpu)
{
- ++vcpu->stat->irq_exits;
+ ++vcpu->stat.irq_exits;
return 1;
}
@@ -5580,7 +5580,7 @@ static int handle_io(struct kvm_vcpu *vcpu)
exit_qualification = vmx_get_exit_qual(vcpu);
string = (exit_qualification & 16) != 0;
- ++vcpu->stat->io_exits;
+ ++vcpu->stat.io_exits;
if (string)
return kvm_emulate_instruction(vcpu, 0);
@@ -5834,7 +5834,7 @@ static int handle_interrupt_window(struct kvm_vcpu *vcpu)
kvm_make_request(KVM_REQ_EVENT, vcpu);
- ++vcpu->stat->irq_window_exits;
+ ++vcpu->stat.irq_window_exits;
return 1;
}
@@ -6012,7 +6012,7 @@ static int handle_nmi_window(struct kvm_vcpu *vcpu)
return -EIO;
exec_controls_clearbit(to_vmx(vcpu), CPU_BASED_NMI_WINDOW_EXITING);
- ++vcpu->stat->nmi_window_exits;
+ ++vcpu->stat.nmi_window_exits;
kvm_make_request(KVM_REQ_EVENT, vcpu);
return 1;
@@ -6276,7 +6276,7 @@ static int handle_notify(struct kvm_vcpu *vcpu)
unsigned long exit_qual = vmx_get_exit_qual(vcpu);
bool context_invalid = exit_qual & NOTIFY_VM_CONTEXT_INVALID;
- ++vcpu->stat->notify_window_exits;
+ ++vcpu->stat.notify_window_exits;
/*
* Notify VM exit happened while executing iret from NMI,
@@ -7637,7 +7637,7 @@ fastpath_t vmx_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags)
*/
if (vcpu->arch.nested_run_pending &&
!vmx_get_exit_reason(vcpu).failed_vmentry)
- ++vcpu->stat->nested_run;
+ ++vcpu->stat.nested_run;
vcpu->arch.nested_run_pending = 0;
}
diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index b66e292c80d6..4527f04c6617 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -625,8 +625,6 @@ void kvm_xen_inject_vcpu_vector(struct kvm_vcpu *v)
irq.shorthand = APIC_DEST_NOSHORT;
irq.delivery_mode = APIC_DM_FIXED;
irq.level = 1;
- irq.plane = v->plane;
-
kvm_irq_delivery_to_apic(v->plane, NULL, &irq);
}
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 82189353ef35..dfc9c7dab21e 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1686,6 +1686,8 @@ struct kvm_memory_attributes {
};
#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)
+#define KVM_MEMORY_ATTRIBUTE_NO_WRITE (1ULL << 4)
+#define KVM_MEMORY_ATTRIBUTE_NO_EXEC (1ULL << 5)
/*
* Per-plane memory protection attributes (VM planes / VBS).
diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
index 229154e06cd0..db57c5766ab6 100644
--- a/virt/kvm/guest_memfd.c
+++ b/virt/kvm/guest_memfd.c
@@ -827,7 +827,6 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
struct file *file, gfn_t gfn, struct page *src_page,
kvm_gmem_populate_cb post_populate, void *opaque)
{
- struct kvm_plane *plane0 = kvm->planes[0];
pgoff_t index = kvm_gmem_get_index(slot, gfn);
struct folio *folio;
kvm_pfn_t pfn;
@@ -843,7 +842,7 @@ static long __kvm_gmem_populate(struct kvm *kvm, struct kvm_memory_slot *slot,
folio_unlock(folio);
- if (!kvm_range_has_memory_attributes(plane0, gfn, gfn + 1,
+ if (!kvm_range_has_memory_attributes(kvm, gfn, gfn + 1,
KVM_MEMORY_ATTRIBUTE_PRIVATE,
KVM_MEMORY_ATTRIBUTE_PRIVATE)) {
ret = -EINVAL;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 21/42] KVM: x86: exit VM planes and VBS hypercalls to userspace
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (19 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 20/42] kvm: x86: fix merged plane API/stat build regressions Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 22/42] kexec: block legacy kexec_load when VBS is active Sriram Nambakam
` (20 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
arch/x86/kvm/x86.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index d4210053e6b8..00dcdd0e22a4 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -119,10 +119,11 @@ u64 __read_mostly efer_reserved_bits = ~((u64)(EFER_SCE | EFER_LME | EFER_LMA));
static u64 __read_mostly efer_reserved_bits = ~((u64)EFER_SCE);
#endif
-#define KVM_EXIT_HYPERCALL_VALID_MASK (BIT(KVM_HC_MAP_GPA_RANGE) | \
- BIT(KVM_HC_VM_PLANES_CONFIG) | \
- BIT(KVM_HC_VM_PLANES_ACTIVATE) | \
- BIT(KVM_HC_VBS_VTL_CALL))
+#define KVM_EXIT_HYPERCALL_VALID_MASK \
+ ((1 << KVM_HC_MAP_GPA_RANGE) | \
+ (1 << KVM_HC_VM_PLANES_CONFIG) | \
+ (1 << KVM_HC_VM_PLANES_ACTIVATE) | \
+ (1 << KVM_HC_VBS_VTL_CALL))
#define KVM_CAP_PMU_VALID_MASK KVM_PMU_CAP_DISABLE
@@ -478,7 +479,7 @@ static unsigned int num_msr_based_features;
unsigned kvm_x86_default_max_planes(struct kvm *kvm)
{
- return 1;
+ return 2;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_default_max_planes);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 22/42] kexec: block legacy kexec_load when VBS is active
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (20 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 21/42] KVM: x86: exit VM planes and VBS hypercalls to userspace Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 23/42] kvm: x86: fix merged plane API/stat build regressions Sriram Nambakam
` (19 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Legacy kexec_load accepts raw memory segments and bypasses the
file-based VBS validation path. Reject non-crash usage when a VBS
backend is registered to prevent untrusted payload staging.
Crash dumps (KEXEC_ON_CRASH) are still permitted since they serve a
different purpose and do not replace the running kernel.
Returns -EKEYREJECTED so userspace can distinguish VBS policy denial
from permission errors.
---
kernel/kexec.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/kernel/kexec.c b/kernel/kexec.c
index 90756dc6339b..049afe1e1f5d 100644
--- a/kernel/kexec.c
+++ b/kernel/kexec.c
@@ -16,6 +16,7 @@
#include <linux/syscalls.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
+#include <linux/vbs.h>
#include "kexec_internal.h"
@@ -205,6 +206,15 @@ static inline int kexec_load_check(unsigned long nr_segments,
int image_type = (flags & KEXEC_ON_CRASH) ?
KEXEC_TYPE_CRASH : KEXEC_TYPE_DEFAULT;
int result;
+ bool crash_kexec = !!(flags & KEXEC_ON_CRASH);
+
+ /*
+ * Legacy kexec_load accepts raw memory segments and bypasses the
+ * file-based VBS validation path. Reject non-crash usage when VBS
+ * is active to prevent untrusted payload staging.
+ */
+ if (vbs_available() && !crash_kexec)
+ return -EKEYREJECTED;
/* We only trust the superuser with rebooting the system. */
if (!kexec_load_permitted(image_type))
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 23/42] kvm: x86: fix merged plane API/stat build regressions
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (21 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 22/42] kexec: block legacy kexec_load when VBS is active Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 24/42] KVM: planes: expose memory-attribute setting to in-kernel callers Sriram Nambakam
` (18 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
---
include/uapi/linux/kvm.h | 23 -----------------------
1 file changed, 23 deletions(-)
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index dfc9c7dab21e..348628c7b17e 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1689,29 +1689,6 @@ struct kvm_memory_attributes {
#define KVM_MEMORY_ATTRIBUTE_NO_WRITE (1ULL << 4)
#define KVM_MEMORY_ATTRIBUTE_NO_EXEC (1ULL << 5)
-/*
- * Per-plane memory protection attributes (VM planes / VBS).
- * These control EPT R/W/X permissions enforced by the hypervisor on
- * behalf of a higher-privilege plane (e.g., plane-1 restricting plane-0).
- */
-#define KVM_MEMORY_ATTRIBUTE_NO_WRITE (1ULL << 4)
-#define KVM_MEMORY_ATTRIBUTE_NO_EXEC (1ULL << 5)
-
-/*
- * Set memory attributes on a specific plane's address space.
- * Used by a higher-privilege plane to restrict a lower-privilege plane's
- * EPT permissions (e.g., plane-1 making plane-0 kernel text read-only).
- */
-struct kvm_plane_memory_attributes {
- __u32 plane; /* target plane index */
- __u32 flags; /* must be 0 */
- __u64 address; /* GPA (page-aligned) */
- __u64 size; /* size in bytes (page-aligned) */
- __u64 attributes; /* KVM_MEMORY_ATTRIBUTE_NO_WRITE / NO_EXEC */
-};
-
-#define KVM_SET_PLANE_MEMORY_ATTRIBUTES _IOW(KVMIO, 0xd6, struct kvm_plane_memory_attributes)
-
#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
#define GUEST_MEMFD_FLAG_INIT_SHARED (1ULL << 1)
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 24/42] KVM: planes: expose memory-attribute setting to in-kernel callers
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (22 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 23/42] kvm: x86: fix merged plane API/stat build regressions Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 25/42] vm_planes: drop unused per-plane vcpu_count Sriram Nambakam
` (17 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
A higher-privilege plane needs to restrict a lower plane's access to
guest memory by setting NO_WRITE / NO_EXEC EPT attributes (e.g. HEKI
sealing plane-0 text/rodata). The enforcement already lives in
kvm_plane_filter_pte_access(); wire up the set side:
- advertise KVM_MEMORY_ATTRIBUTE_NO_WRITE / NO_EXEC from
kvm_supported_mem_attributes() when CONFIG_VM_PLANES is enabled, so
userspace and in-kernel callers know the attributes are available.
- make kvm_vm_set_mem_attributes() non-static and declare it in
kvm_host.h so an in-kernel secure-plane caller can apply attributes
without going through the ioctl path.
No functional change for non-plane builds.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
include/linux/kvm_host.h | 2 ++
virt/kvm/kvm_main.c | 21 +++++++++++++++++----
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index e989b293a34a..82e557e66152 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -2703,6 +2703,8 @@ static inline unsigned long kvm_get_memory_attributes(struct kvm *kvm, gfn_t gfn
bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
unsigned long mask, unsigned long attrs);
+int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
+ unsigned long attributes);
bool kvm_arch_pre_set_memory_attributes(struct kvm *kvm,
struct kvm_gfn_range *range);
bool kvm_arch_post_set_memory_attributes(struct kvm *kvm,
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index f703545a7e80..9623ab8ebd9e 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2603,10 +2603,23 @@ static int kvm_vm_ioctl_clear_dirty_log(struct kvm *kvm,
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
static u64 kvm_supported_mem_attributes(struct kvm *kvm)
{
+ u64 attrs = 0;
+
if (!kvm || kvm_arch_has_private_mem(kvm))
- return KVM_MEMORY_ATTRIBUTE_PRIVATE;
+ attrs |= KVM_MEMORY_ATTRIBUTE_PRIVATE;
- return 0;
+#ifdef CONFIG_VM_PLANES
+ /*
+ * Cross-plane EPT protection: a higher-privilege plane may restrict
+ * a lower plane's access via NO_WRITE / NO_EXEC (e.g. HEKI sealing
+ * plane-0 kernel text and rodata). The enforcement path lives in
+ * kvm_plane_filter_pte_access(); advertise the attributes here so
+ * KVM_SET_MEMORY_ATTRIBUTES accepts them.
+ */
+ attrs |= KVM_MEMORY_ATTRIBUTE_NO_WRITE | KVM_MEMORY_ATTRIBUTE_NO_EXEC;
+#endif
+
+ return attrs;
}
/*
@@ -2716,8 +2729,8 @@ static bool kvm_pre_set_memory_attributes(struct kvm *kvm,
}
/* Set @attributes for the gfn range [@start, @end). */
-static int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
- unsigned long attributes)
+int kvm_vm_set_mem_attributes(struct kvm *kvm, gfn_t start, gfn_t end,
+ unsigned long attributes)
{
struct kvm_mmu_notifier_range pre_set_range = {
.start = start,
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 25/42] vm_planes: drop unused per-plane vcpu_count
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (23 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 24/42] KVM: planes: expose memory-attribute setting to in-kernel callers Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 26/42] drivers/virt: add VBS secure-plane park loop Sriram Nambakam
` (16 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Under the in-kernel "Option B" secure-plane model the secure plane runs
on plane-0's existing vCPU thread and is left stopped until the first VTL
call, so a plane never owns a distinct set of vCPUs. The vcpu_count field
in struct vm_plane_config (and the matching parse-state copy) is no longer
consulted by anything in the kernel: drop the field, its VCPU_COUNT config
key parsing, its zero-init and its presence check.
No functional change; the value was already unused on the boot path.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
include/linux/vm_planes.h | 1 -
init/vm_planes.c | 11 -----------
2 files changed, 12 deletions(-)
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index 47f05fa80039..1130557cf5aa 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -20,7 +20,6 @@ struct vm_plane_config {
phys_addr_t load_offset;
phys_addr_t memory_size;
phys_addr_t entry_point;
- unsigned int vcpu_count;
unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
char cmdline[VM_PLANE_CMDLINE_MAX];
diff --git a/init/vm_planes.c b/init/vm_planes.c
index 274c0015fe76..10c7facdb1af 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -25,7 +25,6 @@ static bool __initdata enable_vm_planes_requested;
struct vm_plane_parse_state {
phys_addr_t load_offset;
phys_addr_t memory_size;
- unsigned int vcpu_count;
unsigned int kernel_format;
char kernel[VM_PLANE_KERNEL_NAME_MAX];
char cmdline[VM_PLANE_CMDLINE_MAX];
@@ -275,14 +274,6 @@ static int __init parse_plane_cfg_line(const char *line, size_t len,
return 0;
}
- if (!strcmp(key, "VCPU_COUNT")) {
- if (parsed_u64 == 0 || parsed_u64 > UINT_MAX)
- return -EINVAL;
- plane_cfg[plane_id].vcpu_count = (unsigned int)parsed_u64;
- state[plane_id].vcpu_count = (unsigned int)parsed_u64;
- return 0;
- }
-
return -ENOENT;
}
@@ -314,7 +305,6 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
for (i = 0; i < *plane_count; i++) {
state[i].load_offset = VM_PLANES_UNSET_VALUE;
state[i].memory_size = VM_PLANES_UNSET_VALUE;
- state[i].vcpu_count = 0;
state[i].kernel[0] = '\0';
state[i].cmdline[0] = '\0';
}
@@ -336,7 +326,6 @@ static int __init parse_vm_planes_kconfig(const char *buf, size_t len,
for (i = 1; i < *plane_count; i++) {
if (state[i].load_offset == VM_PLANES_UNSET_VALUE ||
state[i].memory_size == VM_PLANES_UNSET_VALUE ||
- !state[i].vcpu_count ||
!state[i].kernel[0])
return -EINVAL;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 26/42] drivers/virt: add VBS secure-plane park loop
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (24 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 25/42] vm_planes: drop unused per-plane vcpu_count Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 27/42] KVM: planes: add arch-neutral in-kernel plane switch helper Sriram Nambakam
` (15 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add a minimal, self-contained driver that lets an otherwise ordinary
kernel act as the secure plane (plane >0) of a KVM VM-planes guest. When
the "vbs_park" command-line option is present, a kernel thread hands
control back to the normal plane via KVM_HC_VBS_VTL_RETURN and then
services VTL calls from a shared calling area, acknowledging each as a
no-op.
This is deliberately independent of the full VBS/HEKI stack (CONFIG_VBS):
it implements only the park/dispatch handshake so that any secure kernel
(or a future SVSM) can act as plane 1. Gated behind CONFIG_VBS_PARK.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
drivers/virt/Kconfig | 15 +++++
drivers/virt/Makefile | 1 +
drivers/virt/vbs_park.c | 136 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 152 insertions(+)
create mode 100644 drivers/virt/vbs_park.c
diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index 52eb7e4ba71f..88e40eaba1c2 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -13,6 +13,21 @@ menuconfig VIRT_DRIVERS
if VIRT_DRIVERS
+config VBS_PARK
+ bool "KVM VM-planes secure-plane park loop"
+ depends on X86 && KVM_GUEST
+ help
+ Minimal in-kernel handler for the secure plane (plane >0) of a KVM
+ VM-planes guest. When enabled and the "vbs_park" kernel command-line
+ option is present, a kernel thread hands control back to the normal
+ plane via the KVM_HC_VBS_VTL_RETURN hypercall and then services VTL
+ calls from a shared calling area.
+
+ This is independent of the full VBS/HEKI stack (CONFIG_VBS): it
+ implements only the park/dispatch handshake so that any secure kernel
+ can act as plane 1. Calls are acknowledged as no-ops. Say N unless
+ this kernel is used as a VM-planes secure plane.
+
config VMGENID
tristate "Virtual Machine Generation ID driver"
default y
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index f29901bd7820..fa91899a356d 100644
--- a/drivers/virt/Makefile
+++ b/drivers/virt/Makefile
@@ -5,6 +5,7 @@
obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o
obj-$(CONFIG_VMGENID) += vmgenid.o
+obj-$(CONFIG_VBS_PARK) += vbs_park.o
obj-y += vboxguest/
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
diff --git a/drivers/virt/vbs_park.c b/drivers/virt/vbs_park.c
new file mode 100644
index 000000000000..fabb6beeea7b
--- /dev/null
+++ b/drivers/virt/vbs_park.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * vbs_park - minimal KVM VM-planes secure-plane park loop
+ *
+ * This provides only the secure-plane (plane >0) side of the VM-planes
+ * park/dispatch handshake so that an otherwise ordinary kernel can act as
+ * plane 1. It is deliberately independent of the full VBS/HEKI stack
+ * (CONFIG_VBS): it implements no security policy. Its single job is to hand
+ * control back to the normal plane (plane 0) via the KVM_HC_VBS_VTL_RETURN
+ * hypercall and then service VTL calls from the shared calling area.
+ *
+ * Control flow (all within plane 0's single KVM_RUN; see
+ * arch/x86/kvm/x86.c __kvm_emulate_hypercall):
+ *
+ * plane 0 KVM plane 1 (here)
+ * ------- --- --------------
+ * fill calling area
+ * HC_VBS_VTL_CALL(ca_gpa) ─────▶ switch_plane ───────────▶ resume in
+ * (RAX := ca_gpa) vtl_return()
+ * handle call_id
+ * write ca->status
+ * resume after VTL_CALL ◀─────── switch_plane ◀─────────── HC_VBS_VTL_RETURN
+ *
+ * Activated by the "vbs_park" kernel command-line option; without it this
+ * kernel boots normally and never parks.
+ */
+
+#define pr_fmt(fmt) "vbs-park: " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/kvm_para.h>
+#include <asm/kvm_para.h>
+
+/*
+ * Shared-memory calling area. MUST match struct vbs_kvm_ca in
+ * security/vbs/kvm_planes.c (the normal-plane <-> secure-plane wire ABI):
+ *
+ * [ call_pending | call_id | status | arg_size | resp_size | buffer ]
+ */
+struct vtl_ca {
+ __u8 call_pending; /* 1 while call is in flight */
+ __u8 rsvd[3];
+ __u32 call_id; /* request id (set by caller) */
+ __s32 status; /* return code (set by responder) */
+ __u32 arg_size; /* request payload size */
+ __u32 resp_size; /* response payload size */
+ __u8 buffer[]; /* request data in, response data out */
+} __packed;
+
+/* Set from the "vbs_park" kernel command-line option. */
+static bool vbs_park_active __ro_after_init;
+
+static int __init vbs_park_setup(char *str)
+{
+ vbs_park_active = true;
+ return 1;
+}
+__setup("vbs_park", vbs_park_setup);
+
+/*
+ * Park the secure plane and hand control back to the normal plane. On the
+ * next VTL call KVM resumes us here with the calling-area GPA in the
+ * hypercall return value (RAX). @status is carried for tracing only; the
+ * real result is already in the calling area.
+ */
+static u64 vtl_return(long status)
+{
+ return kvm_hypercall1(KVM_HC_VBS_VTL_RETURN, (unsigned long)status);
+}
+
+static int vbs_park_fn(void *unused)
+{
+ long status = 0;
+
+ pr_info("secure-plane park loop started\n");
+
+ for (;;) {
+ struct vtl_ca *ca;
+ u64 ca_gpa;
+
+ /* Park; resume with the next request's calling-area GPA. */
+ ca_gpa = vtl_return(status);
+ if (!ca_gpa) {
+ status = -EINVAL;
+ continue;
+ }
+
+ ca = memremap(ca_gpa, PAGE_SIZE, MEMREMAP_WB);
+ if (!ca) {
+ pr_err_ratelimited("failed to map calling area 0x%llx\n",
+ ca_gpa);
+ status = -EFAULT;
+ continue;
+ }
+
+ /*
+ * No security policy lives here: acknowledge the call as a
+ * no-op so the normal plane can make progress. Replace this
+ * with real handlers (or move plane 1 to a dedicated SVSM) to
+ * enforce actual VBS semantics.
+ */
+ pr_info_ratelimited("VTL call id=0x%x arg_size=%u (no-op)\n",
+ ca->call_id, ca->arg_size);
+ ca->status = 0;
+ ca->resp_size = 0;
+ status = 0;
+
+ memunmap(ca);
+ }
+
+ return 0;
+}
+
+static int __init vbs_park_init(void)
+{
+ struct task_struct *t;
+
+ if (!vbs_park_active)
+ return 0;
+
+ t = kthread_run(vbs_park_fn, NULL, "vbs-park");
+ if (IS_ERR(t)) {
+ pr_err("failed to start park loop: %ld\n", PTR_ERR(t));
+ return PTR_ERR(t);
+ }
+
+ return 0;
+}
+late_initcall(vbs_park_init);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 27/42] KVM: planes: add arch-neutral in-kernel plane switch helper
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (25 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 26/42] drivers/virt: add VBS secure-plane park loop Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 28/42] KVM: x86: add VBS VTL call/return and cross-plane set-mem-attrs hypercalls Sriram Nambakam
` (14 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Factor the in-kernel plane switch out of the SEV-SNP VMPL path into a
generic kvm_vcpu_switch_plane(). Both vCPUs share the same vcpu->common,
so the switch only validates the sibling relationship, flips the
per-plane runnable/stopped state, and returns 1 to keep the caller
inside KVM_RUN; the run loop then re-selects the target plane via
kvm_vcpu_select_plane().
This is the common core shared by all secure-plane backends (SEV-SNP
VMPL today, VBS/VTL next); vendor-specific state preparation stays in
the caller. Convert __sev_snp_run_vmpl() to use it.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
arch/x86/kvm/svm/sev.c | 13 ++++++-------
include/linux/kvm_host.h | 1 +
virt/kvm/kvm_main.c | 27 +++++++++++++++++++++++++++
3 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index 53e76d22eb08..b9b0bbb72394 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4507,21 +4507,20 @@ static int __sev_snp_run_vmpl(struct vcpu_svm *svm, unsigned int vmpl)
{
struct kvm_vcpu *vcpu = &svm->vcpu;
struct kvm_vcpu *target = vcpu->common->vcpus[vmpl];
- struct vcpu_svm *target_svm = to_svm(target);
+ struct vcpu_svm *target_svm;
if (!target)
return -EINVAL;
- /* Mark current plane as stopped so it is not selected */
+ target_svm = to_svm(target);
+
+ /* SEV-specific preparation for the target VMPL before switching. */
kvm_set_mp_state(target, KVM_MP_STATE_RUNNABLE);
/* In case KVM_REQ_UPDATE_PROTECTED_GUEST_STATE is set - mark the new VMSA as runnable */
target_svm->sev_es.snp_ap_runnable = true;
- kvm_vcpu_set_plane_runnable(target);
- kvm_vcpu_set_plane_stopped(vcpu);
-
- kvm_make_request(KVM_REQ_PLANE_RESCHED, vcpu);
- return 1;
+ /* Perform the arch-neutral in-kernel plane switch. */
+ return kvm_vcpu_switch_plane(vcpu, target);
}
static int sev_snp_run_vmpl(struct vcpu_svm *svm)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 82e557e66152..c6cf2b6c0076 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -455,6 +455,7 @@ struct kvm_vcpu {
void kvm_vcpu_set_plane_runnable(struct kvm_vcpu *vcpu);
void kvm_vcpu_set_plane_stopped(struct kvm_vcpu *vcpu);
+int kvm_vcpu_switch_plane(struct kvm_vcpu *vcpu, struct kvm_vcpu *target);
struct kvm_vcpu *kvm_vcpu_select_plane(struct kvm_vcpu *vcpu);
static inline bool kvm_vcpu_wants_to_run(struct kvm_vcpu *vcpu)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 9623ab8ebd9e..553c282500fd 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -5039,6 +5039,33 @@ void kvm_vcpu_set_plane_stopped(struct kvm_vcpu *vcpu)
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_set_plane_stopped);
+/*
+ * Switch the logical CPU from the currently-running plane (@vcpu) to a sibling
+ * plane (@target) without leaving KVM_RUN. Both vCPUs share the same
+ * vcpu->common, so this only flips the per-plane runnable/stopped state and
+ * requests a plane reschedule; the run loop in kvm_arch_vcpu_ioctl_run() then
+ * re-selects @target via kvm_vcpu_select_plane() and re-enters the guest.
+ *
+ * This is the arch-neutral core of the in-kernel plane switch shared by all
+ * secure-plane backends (SEV-SNP VMPL, VBS/VTL on Intel and AMD, and, in the
+ * future, Arm stage-2). Any vendor-specific state preparation must be done by
+ * the caller before invoking this helper.
+ *
+ * Returns 1 to keep the caller inside KVM_RUN, or -EINVAL if @target is not a
+ * valid sibling plane of @vcpu.
+ */
+int kvm_vcpu_switch_plane(struct kvm_vcpu *vcpu, struct kvm_vcpu *target)
+{
+ if (!target || target->common != vcpu->common)
+ return -EINVAL;
+
+ kvm_vcpu_set_plane_runnable(target);
+ kvm_vcpu_set_plane_stopped(vcpu);
+
+ return 1;
+}
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_switch_plane);
+
struct kvm_vcpu *kvm_vcpu_select_plane(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_common *common = vcpu->common;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 28/42] KVM: x86: add VBS VTL call/return and cross-plane set-mem-attrs hypercalls
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (26 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 27/42] KVM: planes: add arch-neutral in-kernel plane switch helper Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 29/42] init/vm_planes: set up planes from rootfs_initcall and load ELF payloads Sriram Nambakam
` (13 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add the in-kernel handling for the VBS secure-plane hypercalls so the
plane switch happens without bouncing through userspace:
- KVM_HC_VBS_VTL_CALL: the normal plane (plane 0) records the
calling-area GPA and switches to the secure plane. While the secure
plane is still booting the call is parked (vtl_call_pending) and
delivered once the plane parks itself; once ready (vtl_plane_ready)
the GPA is delivered directly via kvm_vcpu_switch_plane().
- KVM_HC_VBS_VTL_RETURN: the secure plane parks and hands control back
to plane 0, marking itself ready and delivering any pending call.
- KVM_HC_VBS_SET_MEM_ATTRS: the secure plane applies cross-plane EPT
restrictions to a lower plane via kvm_vm_set_mem_attributes()
(rejected from plane 0).
Track the per-CPU bootstrap state (vtl_plane_ready, vtl_call_pending,
vtl_call_ca) in kvm_vcpu_common and assign the new hypercall numbers
KVM_HC_VBS_VTL_RETURN (16) and KVM_HC_VBS_SET_MEM_ATTRS (17).
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
arch/x86/kvm/x86.c | 139 +++++++++++++++++++++++++++++++++-
include/linux/kvm_host.h | 17 +++++
include/uapi/linux/kvm_para.h | 2 +
3 files changed, 156 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index eb82dde62399..3c73ab1dcfe8 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10588,9 +10588,145 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
vcpu->arch.complete_userspace_io = complete_hypercall;
return 0;
}
+ case KVM_HC_VBS_VTL_CALL:
+#ifdef CONFIG_VM_PLANES
+ /*
+ * Runtime VBS/VTL call from the normal world (plane 0) into the
+ * secure plane. Serviced in-kernel by switching to the secure
+ * plane (plane 1) — no userspace round trip. This is
+ * arch-neutral: it works for both Intel (VMX) and AMD (SVM), and
+ * mirrors the SEV-SNP in-kernel VMPL switch. a0 carries the
+ * guest-physical address of the shared calling area.
+ *
+ * Two cases:
+ * - Secure plane already booted and parked in its dispatch loop
+ * (vtl_plane_ready): deliver the calling-area GPA directly in
+ * RAX (its pending VTL return value) and switch to it.
+ * - Secure plane not booted yet (bootstrap): record the call as
+ * pending and switch to the secure plane so it boots; it will
+ * pick up the pending GPA when it reaches its first VTL return.
+ *
+ * If there is no secure plane configured at all, fall through to
+ * the userspace path so QEMU can service the call.
+ */
+ if (vcpu->plane_level == 0) {
+ struct kvm_vcpu_common *common = vcpu->common;
+ struct kvm_vcpu *secure = common->vcpus[1];
+
+ if (secure) {
+ common->vtl_call_ca = a0;
+
+ if (common->vtl_plane_ready) {
+ /* Parked in vtl_return: deliver now. */
+ kvm_rax_write(secure, a0);
+ common->vtl_call_pending = false;
+ } else {
+ /* Still booting: deliver on readiness. */
+ common->vtl_call_pending = true;
+ }
+
+ if (kvm_vcpu_switch_plane(vcpu, secure) == 1) {
+ ret = 0;
+ goto out;
+ }
+ ret = -KVM_EINVAL;
+ goto out;
+ }
+ }
+#endif /* CONFIG_VM_PLANES */
+ goto vtl_userspace_exit;
+ case KVM_HC_VBS_VTL_RETURN:
+#ifdef CONFIG_VM_PLANES
+ /*
+ * The secure plane (plane >0) hands control back to plane 0
+ * in-kernel. This covers three situations:
+ * - Bootstrap "ready": the secure plane has just booted and is
+ * issuing its first VTL return to announce it is parked.
+ * - Normal completion: it has finished servicing a VTL call;
+ * the result is already in the shared calling area.
+ * - A call that arrived while the secure plane was still booting
+ * is now delivered (vtl_call_pending) by returning its
+ * calling-area GPA in RAX and keeping the secure plane running.
+ * a0 is an optional status carried for tracing only.
+ */
+ if (vcpu->plane_level == 0) {
+ ret = -KVM_EPERM;
+ goto out;
+ } else {
+ struct kvm_vcpu_common *common = vcpu->common;
+
+ common->vtl_plane_ready = true;
+
+ if (common->vtl_call_pending) {
+ /*
+ * Deliver the call that triggered the secure
+ * plane's boot: return its calling-area GPA and
+ * stay in the secure plane to service it. The
+ * GPA is delivered as this hypercall's return
+ * value (RAX) via the normal completion path; do
+ * not write RAX directly here, as the completion
+ * handler would overwrite it with hypercall.ret.
+ */
+ common->vtl_call_pending = false;
+ ret = common->vtl_call_ca;
+ goto out;
+ }
+
+ if (kvm_vcpu_switch_plane(vcpu, common->vcpus[0]) == 1) {
+ ret = 0;
+ goto out;
+ }
+ }
+#endif /* CONFIG_VM_PLANES */
+ ret = -KVM_EINVAL;
+ goto out;
+ case KVM_HC_VBS_SET_MEM_ATTRS:
+#if defined(CONFIG_VM_PLANES) && defined(CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES)
+ /*
+ * The secure plane (plane >0) enforces EPT permissions on the
+ * normal plane's memory. It cannot issue the host
+ * KVM_SET_MEMORY_ATTRIBUTES ioctl, so it asks KVM to do it via
+ * this hypercall. Only a higher-privilege plane may call it.
+ *
+ * a0 = guest-physical address (page aligned)
+ * a1 = region size in bytes (page aligned)
+ * a2 = access bits to retain for lower planes:
+ * bit0 read (implicit), bit1 write, bit2 exec
+ * (matches VBS_MEM_READ/WRITE/EXEC)
+ */
+ if (vcpu->plane_level == 0) {
+ ret = -KVM_EPERM;
+ goto out;
+ }
+
+ if (!PAGE_ALIGNED(a0) || !PAGE_ALIGNED(a1) || a1 == 0 ||
+ a0 + a1 < a0) {
+ ret = -KVM_EINVAL;
+ goto out;
+ } else {
+ unsigned long attrs = 0;
+ gfn_t start = a0 >> PAGE_SHIFT;
+ gfn_t end = (a0 + a1) >> PAGE_SHIFT;
+
+ if (!(a2 & BIT(1)))
+ attrs |= KVM_MEMORY_ATTRIBUTE_NO_WRITE;
+ if (!(a2 & BIT(2)))
+ attrs |= KVM_MEMORY_ATTRIBUTE_NO_EXEC;
+
+ if (kvm_vm_set_mem_attributes(vcpu->kvm, start, end,
+ attrs))
+ ret = -KVM_EINVAL;
+ else
+ ret = 0;
+ goto out;
+ }
+#else
+ ret = -KVM_ENOSYS;
+ goto out;
+#endif /* CONFIG_VM_PLANES && CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
case KVM_HC_VM_PLANES_CONFIG:
case KVM_HC_VM_PLANES_ACTIVATE:
- case KVM_HC_VBS_VTL_CALL: {
+ vtl_userspace_exit:
ret = -KVM_ENOSYS;
if (!user_exit_on_hypercall(vcpu->kvm, nr))
break;
@@ -10609,7 +10745,6 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
WARN_ON_ONCE(vcpu->run->hypercall.flags & KVM_EXIT_HYPERCALL_MBZ);
vcpu->arch.complete_userspace_io = complete_hypercall;
return 0;
- }
default:
ret = -KVM_ENOSYS;
break;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index c6cf2b6c0076..f14d78fd8cd3 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -386,6 +386,23 @@ struct kvm_vcpu_common {
bool plane_switch;
+#ifdef CONFIG_VM_PLANES
+ /*
+ * VBS/VTL secure-plane bootstrap state (per logical CPU).
+ *
+ * @vtl_plane_ready: the secure plane has booted and parked itself in
+ * its dispatch loop (issued its first VTL return).
+ * @vtl_call_pending: a normal-plane VTL call has been registered but
+ * not yet delivered to the secure plane (used while
+ * the secure plane is still booting).
+ * @vtl_call_ca: guest-physical address of the pending call's
+ * shared calling area.
+ */
+ bool vtl_plane_ready;
+ bool vtl_call_pending;
+ u64 vtl_call_ca;
+#endif
+
struct kvm_vcpu_arch_common arch;
};
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 1703238952fb..eec4fce6b33a 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -33,6 +33,8 @@
#define KVM_HC_VM_PLANES_CONFIG 13
#define KVM_HC_VM_PLANES_ACTIVATE 14
#define KVM_HC_VBS_VTL_CALL 15
+#define KVM_HC_VBS_VTL_RETURN 16
+#define KVM_HC_VBS_SET_MEM_ATTRS 17
/*
* hypercalls use architecture specific
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 29/42] init/vm_planes: set up planes from rootfs_initcall and load ELF payloads
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (27 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 28/42] KVM: x86: add VBS VTL call/return and cross-plane set-mem-attrs hypercalls Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 30/42] security/vbs: run backend probe and HEKI seal at rootfs_initcall Sriram Nambakam
` (12 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Move plane setup out of start_kernel()/kernel_init_freeable() and into a
self-registering rootfs_initcall. Link vm_planes.o after initramfs.o so
populate_rootfs() has unpacked the initramfs (which carries the
config-vm-planes file and the plane kernels) before arch_init_vm_planes()
runs. arch_init_vm_planes() becomes static and no longer needs a
declaration in vm_planes.h.
Also load the plane kernels as ELF payloads, copying loadable segments
into the reserved plane memory and zeroing the BSS, replacing the
early_ioremap path with a plain io.h mapping.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
include/linux/vm_planes.h | 1 -
init/Kconfig | 5 ++-
init/Makefile | 5 ++-
init/main.c | 4 --
init/vm_planes.c | 88 +++++++++++++++++++++------------------
5 files changed, 54 insertions(+), 49 deletions(-)
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
index 1130557cf5aa..e33fa03d1d4a 100644
--- a/include/linux/vm_planes.h
+++ b/include/linux/vm_planes.h
@@ -25,7 +25,6 @@ struct vm_plane_config {
char cmdline[VM_PLANE_CMDLINE_MAX];
};
-void __init arch_init_vm_planes(void);
int __init load_vm_plane_kernels(unsigned int plane_count,
struct vm_plane_config *plane_cfg);
diff --git a/init/Kconfig b/init/Kconfig
index 23d9cca334ba..5d76fe852376 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1720,8 +1720,9 @@ config VM_PLANES
Enable hypervisor enabled multi-kernel support.
This allows processing the kernel command-line parameter
- "enable-vm-planes" and, when requested, calling
- arch_init_vm_planes() during start_kernel().
+ "enable-vm-planes" and, when requested, setting up the configured
+ planes from a rootfs_initcall (after the initramfs is populated and
+ before device drivers and late_initcalls run).
The initrd config-vm-planes file is expected to provide per-plane
entries for PLANE_<id>_KERNEL, PLANE_<id>_LOAD_OFFSET, and
diff --git a/init/Makefile b/init/Makefile
index 113133c8cdd7..f6ac312f1e98 100644
--- a/init/Makefile
+++ b/init/Makefile
@@ -6,12 +6,15 @@
ccflags-y := -fno-function-sections -fno-data-sections
obj-y := main.o version.o mounts.o
-obj-y += vm_planes.o
ifneq ($(CONFIG_BLK_DEV_INITRD),y)
obj-y += noinitramfs.o
else
obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o
endif
+# vm_planes.o must link AFTER initramfs.o so that, at rootfs_initcall level,
+# populate_rootfs() (which unpacks the initramfs) runs before
+# arch_init_vm_planes() reads the plane config and kernels from the rootfs.
+obj-y += vm_planes.o
obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
obj-$(CONFIG_INITRAMFS_TEST) += initramfs_test.o
diff --git a/init/main.c b/init/main.c
index 1c779f6d60cc..be188e67c556 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1663,10 +1663,6 @@ static noinline void __init kernel_init_freeable(void)
wait_for_initramfs();
console_on_rootfs();
-#ifdef CONFIG_VM_PLANES
- arch_init_vm_planes();
-#endif
-
/*
* check if there is an early userspace init. If yes, let it do all
* the work
diff --git a/init/vm_planes.c b/init/vm_planes.c
index 10c7facdb1af..64d5ff19a736 100644
--- a/init/vm_planes.c
+++ b/init/vm_planes.c
@@ -12,9 +12,9 @@
#include <linux/vm_planes.h>
#include <linux/elf.h>
#include <linux/mm.h>
+#include <linux/io.h>
#include <asm/cpu.h>
#include <asm/kvm_para.h>
-#include <asm-generic/early_ioremap.h>
#ifdef CONFIG_VM_PLANES
static bool __initdata enable_vm_planes_requested;
@@ -360,44 +360,29 @@ static int __init vm_planes_get_cfg(unsigned int *plane_count,
static int __init copy_to_early_mem(phys_addr_t dest, const void *src,
unsigned long size)
{
- unsigned long slop, clen;
- char *p;
-
- while (size) {
- slop = offset_in_page(dest);
- clen = size;
- if (clen > PAGE_SIZE - slop)
- clen = PAGE_SIZE - slop;
- p = early_memremap(dest & PAGE_MASK, clen + slop);
- if (!p)
- return -ENOMEM;
- memcpy(p + slop, src, clen);
- early_memunmap(p, clen + slop);
- dest += clen;
- src += clen;
- size -= clen;
- }
+ void *p;
+
+ if (!size)
+ return 0;
+ p = memremap(dest, size, MEMREMAP_WB);
+ if (!p)
+ return -ENOMEM;
+ memcpy(p, src, size);
+ memunmap(p);
return 0;
}
static int __init zero_early_mem(phys_addr_t dest, unsigned long size)
{
- unsigned long slop, clen;
- char *p;
-
- while (size) {
- slop = offset_in_page(dest);
- clen = size;
- if (clen > PAGE_SIZE - slop)
- clen = PAGE_SIZE - slop;
- p = early_memremap(dest & PAGE_MASK, clen + slop);
- if (!p)
- return -ENOMEM;
- memset(p + slop, 0, clen);
- early_memunmap(p, clen + slop);
- dest += clen;
- size -= clen;
- }
+ void *p;
+
+ if (!size)
+ return 0;
+ p = memremap(dest, size, MEMREMAP_WB);
+ if (!p)
+ return -ENOMEM;
+ memset(p, 0, size);
+ memunmap(p);
return 0;
}
@@ -616,23 +601,41 @@ int __init __weak alloc_vm_planes(unsigned int plane_count,
int __init __weak activate_vm_planes(unsigned int plane_count,
struct vm_plane_config *plane_cfg) { return -ENOSYS; }
-void __init arch_init_vm_planes(void)
+/*
+ * Set up VM planes during boot.
+ *
+ * This must run after the initramfs is populated (it reads the plane config
+ * and plane kernels from the rootfs) and, crucially, *before* any consumer
+ * that issues a plane switch -- in particular the VBS backend init/seal, and
+ * before any device driver, module, or userspace can touch a plane. A
+ * rootfs_initcall satisfies all of these: it runs immediately after
+ * populate_rootfs() (initramfs ready) and before every device_initcall and
+ * late_initcall. Because init/ links before security/, this also runs before
+ * the VBS probe/HEKI rootfs_initcalls, so the secure plane vcpu exists by the
+ * time the first VTL call is issued.
+ */
+static int __init arch_init_vm_planes(void)
{
unsigned int plane_count = VM_PLANES_DEFAULT_COUNT;
struct vm_plane_config *plane_cfg;
int ret;
if (!enable_vm_planes_requested)
- return;
+ return 0;
- if (!kvm_para_available())
- return;
+ /* Ensure any asynchronous initramfs unpacking has completed. */
+ wait_for_initramfs();
+
+ if (!kvm_para_available()) {
+ pr_info("vm_planes: KVM paravirt unavailable, skipping plane setup\n");
+ return 0;
+ }
ret = vm_planes_get_cfg(&plane_count, &plane_cfg);
if (ret) {
pr_warn("vm_planes: failed to parse %s: %d\n",
VM_PLANES_CONFIG_FILE, ret);
- return;
+ return 0;
}
pr_info("vm_planes: enabling %u planes (ids 0..%u)\n",
@@ -641,18 +644,21 @@ void __init arch_init_vm_planes(void)
ret = alloc_vm_planes(plane_count, plane_cfg);
if (ret) {
pr_err("vm_planes: failed to allocate planes: %d\n", ret);
- return;
+ return 0;
}
ret = load_vm_plane_kernels(plane_count, plane_cfg);
if (ret) {
pr_err("vm_planes: failed to load plane kernels: %d\n", ret);
- return;
+ return 0;
}
ret = activate_vm_planes(plane_count, plane_cfg);
if (ret)
pr_err("vm_planes: failed to activate planes: %d\n", ret);
+
+ return 0;
}
+rootfs_initcall(arch_init_vm_planes);
#endif /* CONFIG_VM_PLANES */
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 30/42] security/vbs: run backend probe and HEKI seal at rootfs_initcall
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (28 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 29/42] init/vm_planes: set up planes from rootfs_initcall and load ELF payloads Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 31/42] security/vbs: pin the VTL call hypercall to CPU0 Sriram Nambakam
` (11 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Move VBS backend probing (vbs_probe_init) from device_initcall and the
HEKI kernel seal (vbs_heki_late_init) from late_initcall to
rootfs_initcall, and link probe.o before core.o so the backend is
registered before the seal runs. At this level the initramfs is
unpacked and the VM planes have been set up (init/ links before
security/), but device drivers, modules and userspace have not started
yet, so the kernel is sealed before anything that could tamper with it
runs.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
security/vbs/Makefile | 5 ++++-
security/vbs/core.c | 9 ++++++++-
security/vbs/probe.c | 9 +++++----
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
index e33052ccde2d..01e831e28ac7 100644
--- a/security/vbs/Makefile
+++ b/security/vbs/Makefile
@@ -1,6 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VBS) += vbs.o
-vbs-y := core.o probe.o
+# probe.o must link before core.o so that, at rootfs_initcall level, the
+# backend is registered (vbs_probe_init) before the HEKI seal runs
+# (vbs_heki_late_init in core.o).
+vbs-y := probe.o core.o
vbs-$(CONFIG_VBS_HEKI) += heki.o
obj-$(CONFIG_VBS_KVM_PLANES) += kvm_planes.o
diff --git a/security/vbs/core.c b/security/vbs/core.c
index 16b5329964f9..1167026fc7d1 100644
--- a/security/vbs/core.c
+++ b/security/vbs/core.c
@@ -195,4 +195,11 @@ static int __init vbs_heki_late_init(void)
return 0;
}
-late_initcall(vbs_heki_late_init);
+/*
+ * Run at rootfs_initcall level (after vbs_probe_init in probe.o, which links
+ * first) so the kernel is sealed before any device driver, module, or
+ * userspace runs. The secure plane vcpu already exists by this point because
+ * arch_init_vm_planes() (init/, links before security/) ran earlier in the
+ * same initcall level.
+ */
+rootfs_initcall(vbs_heki_late_init);
diff --git a/security/vbs/probe.c b/security/vbs/probe.c
index 292f3663a996..14aa3d59310b 100644
--- a/security/vbs/probe.c
+++ b/security/vbs/probe.c
@@ -96,8 +96,9 @@ static int __init vbs_probe_init(void)
}
/*
- * Run at device_initcall level: platform detection (CPUID, MSRs, SMCCC)
- * is complete by this point, but subsystems that consume VBS (module
- * loading, HEKI) have not yet started.
+ * Run at rootfs_initcall level: platform detection (CPUID, MSRs, SMCCC)
+ * is complete by this point, the VM planes have been set up (init/ links
+ * before security/), and subsystems that consume VBS (module loading, HEKI,
+ * device drivers, userspace) have not yet started.
*/
-device_initcall(vbs_probe_init);
+rootfs_initcall(vbs_probe_init);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 31/42] security/vbs: pin the VTL call hypercall to CPU0
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (29 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 30/42] security/vbs: run backend probe and HEKI seal at rootfs_initcall Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 32/42] security/vbs: add secure-plane monitor backend Sriram Nambakam
` (10 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
KVM switches planes per logical CPU, and the secure plane is a single
in-guest kernel that boots only on CPU0's sibling (common->vcpus[1] of
CPU0). A VTL call issued from any other CPU would switch that CPU's
non-existent secure sibling and fail. Drive the hypercall through
work_on_cpu(0, ...) so the calling area build and the plane switch
always land on CPU0 regardless of the caller's CPU. The request/response
marshalling moves into a kvm_vtl_call_ctx run on CPU0; the validation
(calling-area present, arg size) stays on the caller.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
security/vbs/kvm_planes.c | 67 ++++++++++++++++++++++++++++-----------
1 file changed, 48 insertions(+), 19 deletions(-)
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
index 061163a4d303..c41ea7fdb472 100644
--- a/security/vbs/kvm_planes.c
+++ b/security/vbs/kvm_planes.c
@@ -25,6 +25,7 @@
#include <linux/kvm_para.h>
#include <linux/module.h>
#include <linux/string.h>
+#include <linux/workqueue.h>
#include <linux/elf.h>
#include <asm/sections.h>
#include <asm/kvm_para.h>
@@ -59,28 +60,34 @@ static void *kvm_ca_page; /* single calling-area page */
/* ── low-level VTL call ───────────────────────────────────────────────── */
-static int kvm_planes_vtl_call(enum vbs_call_id id,
- const void *arg, size_t arg_size,
- void *resp, size_t resp_size)
+struct kvm_vtl_call_ctx {
+ enum vbs_call_id id;
+ const void *arg;
+ size_t arg_size;
+ void *resp;
+ size_t resp_size;
+};
+
+/*
+ * Issue the VTL call hypercall. MUST run on the BSP (CPU0): KVM switches
+ * planes per logical CPU (the secure sibling is common->vcpus[1] of the
+ * *calling* CPU), and the secure plane is a single in-guest kernel that
+ * boots only on CPU0's sibling. Driven via work_on_cpu() so the hypercall
+ * always lands on CPU0 regardless of the caller's CPU.
+ */
+static long kvm_planes_vtl_call_on_cpu(void *data)
{
- struct vbs_kvm_ca *ca;
+ struct kvm_vtl_call_ctx *ctx = data;
+ struct vbs_kvm_ca *ca = kvm_ca_page;
long hc_ret;
- if (!kvm_ca_page)
- return -ENOMEM;
-
- if (arg_size > VBS_CA_BUF_SIZE)
- return -E2BIG;
-
- ca = kvm_ca_page;
-
/* Build request */
- ca->call_id = id;
- ca->arg_size = arg_size;
+ ca->call_id = ctx->id;
+ ca->arg_size = ctx->arg_size;
ca->status = 0;
ca->resp_size = 0;
- if (arg_size && arg)
- memcpy(ca->buffer, arg, arg_size);
+ if (ctx->arg_size && ctx->arg)
+ memcpy(ca->buffer, ctx->arg, ctx->arg_size);
ca->call_pending = 1;
/* Issue hypercall: pass physical address of the calling area */
@@ -97,14 +104,36 @@ static int kvm_planes_vtl_call(enum vbs_call_id id,
return ca->status;
/* Read response from the same buffer */
- if (resp && resp_size && ca->resp_size) {
- size_t copy = min_t(size_t, resp_size, ca->resp_size);
+ if (ctx->resp && ctx->resp_size && ca->resp_size) {
+ size_t copy = min_t(size_t, ctx->resp_size, ca->resp_size);
- memcpy(resp, ca->buffer, copy);
+ memcpy(ctx->resp, ca->buffer, copy);
}
return 0;
}
+static int kvm_planes_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ struct kvm_vtl_call_ctx ctx = {
+ .id = id,
+ .arg = arg,
+ .arg_size = arg_size,
+ .resp = resp,
+ .resp_size = resp_size,
+ };
+
+ if (!kvm_ca_page)
+ return -ENOMEM;
+
+ if (arg_size > VBS_CA_BUF_SIZE)
+ return -E2BIG;
+
+ /* Pin the plane switch to CPU0's secure sibling (the only booted one). */
+ return work_on_cpu(0, kvm_planes_vtl_call_on_cpu, &ctx);
+}
+
/* ── memory protection ────────────────────────────────────────────────── */
struct vbs_protect_args {
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 32/42] security/vbs: add secure-plane monitor backend
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (30 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 31/42] security/vbs: pin the VTL call hypercall to CPU0 Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 33/42] drivers/virt: rename VBS park loop to secure_monitor Sriram Nambakam
` (9 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add the secure-plane counterpart of the normal-plane kvm_planes backend.
The same kernel image boots as both planes; when started as the secure
plane (via the "vbs_secure_plane" command-line option) this monitor
takes over, parks via KVM_HC_VBS_VTL_RETURN, and dispatches the VTL
calls issued by the normal plane from the shared calling area.
Unlike the minimal CONFIG_VBS_PARK stub, this backend is part of the
full VBS stack and is meant to grow real handlers (seal, memory
protection, attestation). Built with CONFIG_VBS_KVM_PLANES.
Signed-off-by: Sriram Nambakam <snambakam@linux.microsoft.com>
---
security/vbs/Makefile | 1 +
security/vbs/secure_monitor.c | 266 ++++++++++++++++++++++++++++++++++
2 files changed, 267 insertions(+)
create mode 100644 security/vbs/secure_monitor.c
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
index 01e831e28ac7..f24f31727a65 100644
--- a/security/vbs/Makefile
+++ b/security/vbs/Makefile
@@ -7,6 +7,7 @@ vbs-y := probe.o core.o
vbs-$(CONFIG_VBS_HEKI) += heki.o
obj-$(CONFIG_VBS_KVM_PLANES) += kvm_planes.o
+obj-$(CONFIG_VBS_KVM_PLANES) += secure_monitor.o
obj-$(CONFIG_VBS_SEV_SNP) += sev_snp.o
obj-$(CONFIG_VBS_TDX) += tdx.o
obj-$(CONFIG_VBS_HV_VSM) += hv_vsm.o
diff --git a/security/vbs/secure_monitor.c b/security/vbs/secure_monitor.c
new file mode 100644
index 000000000000..c1221ad5019b
--- /dev/null
+++ b/security/vbs/secure_monitor.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS secure-plane monitor — in-guest VTL dispatcher
+ *
+ * This is the secure-plane counterpart of the normal-plane kvm_planes
+ * backend. The SAME kernel image boots as both the normal plane and the
+ * secure plane; when booted as the secure plane (selected via the
+ * "vbs_secure_plane" kernel command-line option) this monitor takes over
+ * and services VTL calls issued by the normal plane.
+ *
+ * "Secure plane" is the highest-privilege plane of the VM (conventionally
+ * plane 1 / VTL1 / VMPL0, but a VM may have up to KVM_MAX_PLANES planes and
+ * the index is not hard-coded here). "Normal plane" is the requesting,
+ * lower-privilege plane (conventionally plane 0).
+ *
+ * Control flow (all within the normal plane's single KVM_RUN, see
+ * arch/x86/kvm/x86.c ____kvm_emulate_hypercall):
+ *
+ * normal plane KVM secure plane
+ * ------------ --- ------------
+ * fill calling area
+ * HC_VBS_VTL_CALL(ca_gpa) ─────▶ switch_plane ───────────▶ resume in
+ * (RAX := ca_gpa) secmon_vtl_return()
+ * dispatch(call_id)
+ * write ca->status
+ * resume after VTL_CALL ◀─────── switch_plane ◀─────────── HC_VBS_VTL_RETURN(status)
+ * read ca->status
+ *
+ * Because all planes of a VM share the same memslots (struct kvm_plane has
+ * no memslots of its own; they live in struct kvm), the secure plane sees
+ * the same guest-physical address space as the normal plane and can read
+ * the calling area and the GPAs referenced by each request directly.
+ */
+
+#define pr_fmt(fmt) "vbs-secmon: " fmt
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/kthread.h>
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/vbs.h>
+#include <linux/kvm_para.h>
+#include <asm/kvm_para.h>
+
+#include "heki.h"
+
+/*
+ * Shared-memory calling area — must match struct vbs_kvm_ca in kvm_planes.c
+ * (this is the normal-plane <-> secure-plane wire ABI).
+ *
+ * [ call_pending | call_id | status | arg_size | resp_size | buffer ]
+ */
+struct vbs_kvm_ca {
+ __u8 call_pending; /* 1 while call is in flight */
+ __u8 rsvd[3];
+ __u32 call_id; /* enum vbs_call_id (set by caller) */
+ __s32 status; /* return code (set by responder) */
+ __u32 arg_size; /* request payload size */
+ __u32 resp_size; /* response payload size */
+ __u8 buffer[]; /* request data in, response data out */
+} __packed;
+
+/* Set from the "vbs_secure_plane" kernel command-line option. */
+static bool secmon_active __ro_after_init;
+
+static int __init secmon_setup(char *str)
+{
+ secmon_active = true;
+ return 1;
+}
+__setup("vbs_secure_plane", secmon_setup);
+
+/*
+ * Park the secure plane and hand control back to the normal plane. On the
+ * next VTL call, KVM resumes us here with the calling-area GPA in the
+ * hypercall return value (RAX). @status is carried for tracing only; the
+ * real result is already in the calling area.
+ */
+static u64 secmon_vtl_return(long status)
+{
+ return kvm_hypercall1(KVM_HC_VBS_VTL_RETURN, (unsigned long)status);
+}
+
+/*
+ * Apply EPT permissions on a normal-plane GPA range from the secure plane.
+ *
+ * The secure plane cannot issue the host KVM_SET_MEMORY_ATTRIBUTES ioctl,
+ * so it asks KVM to do it via the KVM_HC_VBS_SET_MEM_ATTRS hypercall, which
+ * KVM honours only for a higher-privilege plane. @perms carries the access
+ * bits the normal plane should retain (VBS_MEM_*); KVM translates a cleared
+ * write/exec bit into NO_WRITE / NO_EXEC memory attributes.
+ */
+static int secmon_apply_attrs(u64 gpa, u64 size, u32 perms)
+{
+ long ret;
+
+ pr_debug("apply_attrs gpa=0x%llx size=0x%llx perms=%c%c%c\n",
+ gpa, size,
+ (perms & VBS_MEM_READ) ? 'r' : '-',
+ (perms & VBS_MEM_WRITE) ? 'w' : '-',
+ (perms & VBS_MEM_EXEC) ? 'x' : '-');
+
+ ret = kvm_hypercall3(KVM_HC_VBS_SET_MEM_ATTRS, gpa, size, perms);
+ if (ret)
+ return (int)ret;
+
+ return 0;
+}
+
+/* ── per-call handlers ────────────────────────────────────────────────── */
+
+static int secmon_do_protect_memory(const void *arg, u32 arg_size)
+{
+ const struct vbs_protect_memory_req *r = arg;
+
+ if (arg_size < sizeof(*r))
+ return -EINVAL;
+
+ return secmon_apply_attrs(r->gpa, r->size, r->perms);
+}
+
+static int secmon_do_seal_kernel(const void *arg, u32 arg_size)
+{
+ const struct vbs_seal_kernel_req *r = arg;
+ int ret;
+
+ if (arg_size < sizeof(*r))
+ return -EINVAL;
+
+ /* Kernel text: read + execute, no write. */
+ ret = secmon_apply_attrs(r->text_gpa, r->text_size,
+ VBS_MEM_READ | VBS_MEM_EXEC);
+ if (ret)
+ return ret;
+
+ /* Kernel rodata: read only, no write, no execute. */
+ return secmon_apply_attrs(r->rodata_gpa, r->rodata_size,
+ VBS_MEM_READ);
+}
+
+static int secmon_do_set_module_perms(const void *arg, u32 arg_size)
+{
+ const struct vbs_set_module_perms_req *hdr = arg;
+ const struct vbs_module_section *sec;
+ u32 i, n;
+
+ if (arg_size < sizeof(*hdr))
+ return -EINVAL;
+
+ n = hdr->nr_sections;
+ if (arg_size < sizeof(*hdr) + n * sizeof(*sec))
+ return -EINVAL;
+
+ sec = (const struct vbs_module_section *)(hdr + 1);
+ for (i = 0; i < n; i++) {
+ int ret = secmon_apply_attrs(sec[i].gpa, sec[i].size,
+ sec[i].perms);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static long secmon_dispatch(u32 call_id, const void *arg, u32 arg_size,
+ u32 *resp_size)
+{
+ *resp_size = 0;
+
+ switch (call_id) {
+ case VBS_CALL_INIT:
+ case VBS_CALL_SHUTDOWN:
+ return 0;
+
+ case VBS_CALL_PROTECT_MEMORY:
+ return secmon_do_protect_memory(arg, arg_size);
+ case VBS_CALL_SEAL_KERNEL:
+ return secmon_do_seal_kernel(arg, arg_size);
+
+ case VBS_CALL_SET_MODULE_PERMS:
+ return secmon_do_set_module_perms(arg, arg_size);
+
+ /*
+ * Module/kexec validation and key management are acknowledged for
+ * now (mirroring the previous userspace dispatcher); real signature
+ * verification runs here in a later stage.
+ */
+ case VBS_CALL_VALIDATE_MODULE:
+ case VBS_CALL_UNLOAD_MODULE:
+ case VBS_CALL_ADD_KEY:
+ case VBS_CALL_REVOKE_KEY:
+ case VBS_CALL_SEND_CERTS:
+ case VBS_CALL_KEXEC_VALIDATE:
+ case VBS_CALL_KEXEC_INVALIDATE:
+ return 0;
+
+ default:
+ pr_warn_ratelimited("unknown call_id 0x%x\n", call_id);
+ return -ENOSYS;
+ }
+}
+
+/* ── monitor loop ─────────────────────────────────────────────────────── */
+
+static int secmon_monitor_fn(void *unused)
+{
+ long status = 0;
+
+ pr_info("secure monitor started\n");
+
+ for (;;) {
+ struct vbs_kvm_ca *ca;
+ u64 ca_gpa;
+ u32 call_id, arg_size, resp_size = 0;
+
+ /* Park; resume with the next request's calling-area GPA. */
+ ca_gpa = secmon_vtl_return(status);
+ if (!ca_gpa) {
+ status = -EINVAL;
+ continue;
+ }
+
+ ca = memremap(ca_gpa, PAGE_SIZE, MEMREMAP_WB);
+ if (!ca) {
+ pr_err_ratelimited("failed to map calling area 0x%llx\n",
+ ca_gpa);
+ status = -EFAULT;
+ continue;
+ }
+
+ call_id = ca->call_id;
+ arg_size = ca->arg_size;
+ if (arg_size > PAGE_SIZE - sizeof(*ca))
+ arg_size = PAGE_SIZE - sizeof(*ca);
+
+ status = secmon_dispatch(call_id, ca->buffer, arg_size,
+ &resp_size);
+
+ ca->status = (s32)status;
+ ca->resp_size = resp_size;
+
+ memunmap(ca);
+ }
+
+ return 0;
+}
+
+static int __init secmon_init(void)
+{
+ struct task_struct *t;
+
+ if (!secmon_active)
+ return 0;
+
+ t = kthread_run(secmon_monitor_fn, NULL, "vbs-secmon");
+ if (IS_ERR(t)) {
+ pr_err("failed to start secure monitor: %ld\n", PTR_ERR(t));
+ return PTR_ERR(t);
+ }
+
+ return 0;
+}
+late_initcall(secmon_init);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 33/42] drivers/virt: rename VBS park loop to secure_monitor
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (31 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 32/42] security/vbs: add secure-plane monitor backend Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 34/42] x86/realmode: skip the sub-1M trampoline for the VBS secure plane Sriram Nambakam
` (8 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Replace the secure-plane park loop (drivers/virt/vbs_park.c, CONFIG_VBS_PARK)
with drivers/virt/secure_monitor.c (CONFIG_VBS_SECURE_MONITOR), activated by
the "secure_monitor" kernel command-line option. Behaviour is unchanged: a
late_initcall spawns the "vbs-secmon" kthread which hands control back to the
normal plane via KVM_HC_VBS_VTL_RETURN and acknowledges VTL calls as no-ops;
real per-call handlers are plumbed in incrementally.
Also drop the unused security/vbs/secure_monitor.c, which was never wired into
the running secure-plane path (it keyed off "vbs_secure_plane" under
CONFIG_VBS_KVM_PLANES but was never activated).
---
drivers/virt/Kconfig | 16 +-
drivers/virt/Makefile | 2 +-
drivers/virt/{vbs_park.c => secure_monitor.c} | 77 ++---
security/vbs/Makefile | 1 -
security/vbs/secure_monitor.c | 266 ------------------
5 files changed, 52 insertions(+), 310 deletions(-)
rename drivers/virt/{vbs_park.c => secure_monitor.c} (50%)
delete mode 100644 security/vbs/secure_monitor.c
diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index 88e40eaba1c2..5d964f124afe 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -13,20 +13,20 @@ menuconfig VIRT_DRIVERS
if VIRT_DRIVERS
-config VBS_PARK
- bool "KVM VM-planes secure-plane park loop"
+config VBS_SECURE_MONITOR
+ bool "KVM VM-planes secure-plane monitor"
depends on X86 && KVM_GUEST
help
- Minimal in-kernel handler for the secure plane (plane >0) of a KVM
- VM-planes guest. When enabled and the "vbs_park" kernel command-line
+ In-kernel monitor for the secure plane (plane >0) of a KVM VM-planes
+ guest. When enabled and the "secure_monitor" kernel command-line
option is present, a kernel thread hands control back to the normal
plane via the KVM_HC_VBS_VTL_RETURN hypercall and then services VTL
calls from a shared calling area.
- This is independent of the full VBS/HEKI stack (CONFIG_VBS): it
- implements only the park/dispatch handshake so that any secure kernel
- can act as plane 1. Calls are acknowledged as no-ops. Say N unless
- this kernel is used as a VM-planes secure plane.
+ This is independent of the full VBS/HEKI stack (CONFIG_VBS) so that
+ any secure kernel can act as plane 1. Per-call handlers are plumbed
+ in incrementally; until then calls are acknowledged as no-ops. Say N
+ unless this kernel is used as a VM-planes secure plane.
config VMGENID
tristate "Virtual Machine Generation ID driver"
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index fa91899a356d..22d1121ba5bd 100644
--- a/drivers/virt/Makefile
+++ b/drivers/virt/Makefile
@@ -5,7 +5,7 @@
obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o
obj-$(CONFIG_VMGENID) += vmgenid.o
-obj-$(CONFIG_VBS_PARK) += vbs_park.o
+obj-$(CONFIG_VBS_SECURE_MONITOR) += secure_monitor.o
obj-y += vboxguest/
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
diff --git a/drivers/virt/vbs_park.c b/drivers/virt/secure_monitor.c
similarity index 50%
rename from drivers/virt/vbs_park.c
rename to drivers/virt/secure_monitor.c
index fabb6beeea7b..2d181c32c439 100644
--- a/drivers/virt/vbs_park.c
+++ b/drivers/virt/secure_monitor.c
@@ -1,31 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * vbs_park - minimal KVM VM-planes secure-plane park loop
+ * secure_monitor - KVM VM-planes secure-plane monitor
*
- * This provides only the secure-plane (plane >0) side of the VM-planes
- * park/dispatch handshake so that an otherwise ordinary kernel can act as
- * plane 1. It is deliberately independent of the full VBS/HEKI stack
- * (CONFIG_VBS): it implements no security policy. Its single job is to hand
- * control back to the normal plane (plane 0) via the KVM_HC_VBS_VTL_RETURN
- * hypercall and then service VTL calls from the shared calling area.
+ * This is the secure-plane (plane >0) side of the VM-planes park/dispatch
+ * handshake. It lets an otherwise ordinary kernel act as the secure plane
+ * (conventionally plane 1 / VTL1 / VMPL0, though the index is not hard-coded)
+ * without pulling in the full VBS/HEKI stack (CONFIG_VBS). Its single job is
+ * to hand control back to the normal plane (plane 0) via the
+ * KVM_HC_VBS_VTL_RETURN hypercall and then service VTL calls from the shared
+ * calling area.
*
* Control flow (all within plane 0's single KVM_RUN; see
* arch/x86/kvm/x86.c __kvm_emulate_hypercall):
*
- * plane 0 KVM plane 1 (here)
- * ------- --- --------------
+ * normal plane KVM secure plane
+ * ------------ --- ------------
* fill calling area
* HC_VBS_VTL_CALL(ca_gpa) ─────▶ switch_plane ───────────▶ resume in
- * (RAX := ca_gpa) vtl_return()
- * handle call_id
+ * (RAX := ca_gpa) secmon_vtl_return()
+ * dispatch(call_id)
* write ca->status
* resume after VTL_CALL ◀─────── switch_plane ◀─────────── HC_VBS_VTL_RETURN
*
- * Activated by the "vbs_park" kernel command-line option; without it this
- * kernel boots normally and never parks.
+ * Because all planes of a VM share the same memslots (struct kvm_plane has no
+ * memslots of its own; they live in struct kvm), the secure plane sees the
+ * same guest-physical address space as the normal plane and can read the
+ * calling area and the GPAs referenced by each request directly.
+ *
+ * For now every VTL call is acknowledged as a no-op so the normal plane can
+ * make progress; the real per-call handlers (self-protection, HEKI memory
+ * protection, kernel sealing, …) are plumbed in incrementally.
+ *
+ * Activated by the "secure_monitor" kernel command-line option; without it
+ * this kernel boots normally and never parks.
*/
-#define pr_fmt(fmt) "vbs-park: " fmt
+#define pr_fmt(fmt) "vbs-secmon: " fmt
#include <linux/kernel.h>
#include <linux/init.h>
@@ -44,7 +54,7 @@
*
* [ call_pending | call_id | status | arg_size | resp_size | buffer ]
*/
-struct vtl_ca {
+struct vbs_kvm_ca {
__u8 call_pending; /* 1 while call is in flight */
__u8 rsvd[3];
__u32 call_id; /* request id (set by caller) */
@@ -54,15 +64,15 @@ struct vtl_ca {
__u8 buffer[]; /* request data in, response data out */
} __packed;
-/* Set from the "vbs_park" kernel command-line option. */
-static bool vbs_park_active __ro_after_init;
+/* Set from the "secure_monitor" kernel command-line option. */
+static bool secmon_active __ro_after_init;
-static int __init vbs_park_setup(char *str)
+static int __init secmon_setup(char *str)
{
- vbs_park_active = true;
+ secmon_active = true;
return 1;
}
-__setup("vbs_park", vbs_park_setup);
+__setup("secure_monitor", secmon_setup);
/*
* Park the secure plane and hand control back to the normal plane. On the
@@ -70,23 +80,23 @@ __setup("vbs_park", vbs_park_setup);
* hypercall return value (RAX). @status is carried for tracing only; the
* real result is already in the calling area.
*/
-static u64 vtl_return(long status)
+static u64 secmon_vtl_return(long status)
{
return kvm_hypercall1(KVM_HC_VBS_VTL_RETURN, (unsigned long)status);
}
-static int vbs_park_fn(void *unused)
+static int secmon_monitor_fn(void *unused)
{
long status = 0;
- pr_info("secure-plane park loop started\n");
+ pr_info("secure monitor started\n");
for (;;) {
- struct vtl_ca *ca;
+ struct vbs_kvm_ca *ca;
u64 ca_gpa;
/* Park; resume with the next request's calling-area GPA. */
- ca_gpa = vtl_return(status);
+ ca_gpa = secmon_vtl_return(status);
if (!ca_gpa) {
status = -EINVAL;
continue;
@@ -101,10 +111,9 @@ static int vbs_park_fn(void *unused)
}
/*
- * No security policy lives here: acknowledge the call as a
- * no-op so the normal plane can make progress. Replace this
- * with real handlers (or move plane 1 to a dedicated SVSM) to
- * enforce actual VBS semantics.
+ * No handlers are plumbed in yet: acknowledge the call as a
+ * no-op so the normal plane can make progress. Real per-call
+ * dispatch is added incrementally.
*/
pr_info_ratelimited("VTL call id=0x%x arg_size=%u (no-op)\n",
ca->call_id, ca->arg_size);
@@ -118,19 +127,19 @@ static int vbs_park_fn(void *unused)
return 0;
}
-static int __init vbs_park_init(void)
+static int __init secmon_init(void)
{
struct task_struct *t;
- if (!vbs_park_active)
+ if (!secmon_active)
return 0;
- t = kthread_run(vbs_park_fn, NULL, "vbs-park");
+ t = kthread_run(secmon_monitor_fn, NULL, "vbs-secmon");
if (IS_ERR(t)) {
- pr_err("failed to start park loop: %ld\n", PTR_ERR(t));
+ pr_err("failed to start secure monitor: %ld\n", PTR_ERR(t));
return PTR_ERR(t);
}
return 0;
}
-late_initcall(vbs_park_init);
+late_initcall(secmon_init);
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
index f24f31727a65..01e831e28ac7 100644
--- a/security/vbs/Makefile
+++ b/security/vbs/Makefile
@@ -7,7 +7,6 @@ vbs-y := probe.o core.o
vbs-$(CONFIG_VBS_HEKI) += heki.o
obj-$(CONFIG_VBS_KVM_PLANES) += kvm_planes.o
-obj-$(CONFIG_VBS_KVM_PLANES) += secure_monitor.o
obj-$(CONFIG_VBS_SEV_SNP) += sev_snp.o
obj-$(CONFIG_VBS_TDX) += tdx.o
obj-$(CONFIG_VBS_HV_VSM) += hv_vsm.o
diff --git a/security/vbs/secure_monitor.c b/security/vbs/secure_monitor.c
deleted file mode 100644
index c1221ad5019b..000000000000
--- a/security/vbs/secure_monitor.c
+++ /dev/null
@@ -1,266 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * VBS secure-plane monitor — in-guest VTL dispatcher
- *
- * This is the secure-plane counterpart of the normal-plane kvm_planes
- * backend. The SAME kernel image boots as both the normal plane and the
- * secure plane; when booted as the secure plane (selected via the
- * "vbs_secure_plane" kernel command-line option) this monitor takes over
- * and services VTL calls issued by the normal plane.
- *
- * "Secure plane" is the highest-privilege plane of the VM (conventionally
- * plane 1 / VTL1 / VMPL0, but a VM may have up to KVM_MAX_PLANES planes and
- * the index is not hard-coded here). "Normal plane" is the requesting,
- * lower-privilege plane (conventionally plane 0).
- *
- * Control flow (all within the normal plane's single KVM_RUN, see
- * arch/x86/kvm/x86.c ____kvm_emulate_hypercall):
- *
- * normal plane KVM secure plane
- * ------------ --- ------------
- * fill calling area
- * HC_VBS_VTL_CALL(ca_gpa) ─────▶ switch_plane ───────────▶ resume in
- * (RAX := ca_gpa) secmon_vtl_return()
- * dispatch(call_id)
- * write ca->status
- * resume after VTL_CALL ◀─────── switch_plane ◀─────────── HC_VBS_VTL_RETURN(status)
- * read ca->status
- *
- * Because all planes of a VM share the same memslots (struct kvm_plane has
- * no memslots of its own; they live in struct kvm), the secure plane sees
- * the same guest-physical address space as the normal plane and can read
- * the calling area and the GPAs referenced by each request directly.
- */
-
-#define pr_fmt(fmt) "vbs-secmon: " fmt
-
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/kthread.h>
-#include <linux/io.h>
-#include <linux/mm.h>
-#include <linux/types.h>
-#include <linux/errno.h>
-#include <linux/vbs.h>
-#include <linux/kvm_para.h>
-#include <asm/kvm_para.h>
-
-#include "heki.h"
-
-/*
- * Shared-memory calling area — must match struct vbs_kvm_ca in kvm_planes.c
- * (this is the normal-plane <-> secure-plane wire ABI).
- *
- * [ call_pending | call_id | status | arg_size | resp_size | buffer ]
- */
-struct vbs_kvm_ca {
- __u8 call_pending; /* 1 while call is in flight */
- __u8 rsvd[3];
- __u32 call_id; /* enum vbs_call_id (set by caller) */
- __s32 status; /* return code (set by responder) */
- __u32 arg_size; /* request payload size */
- __u32 resp_size; /* response payload size */
- __u8 buffer[]; /* request data in, response data out */
-} __packed;
-
-/* Set from the "vbs_secure_plane" kernel command-line option. */
-static bool secmon_active __ro_after_init;
-
-static int __init secmon_setup(char *str)
-{
- secmon_active = true;
- return 1;
-}
-__setup("vbs_secure_plane", secmon_setup);
-
-/*
- * Park the secure plane and hand control back to the normal plane. On the
- * next VTL call, KVM resumes us here with the calling-area GPA in the
- * hypercall return value (RAX). @status is carried for tracing only; the
- * real result is already in the calling area.
- */
-static u64 secmon_vtl_return(long status)
-{
- return kvm_hypercall1(KVM_HC_VBS_VTL_RETURN, (unsigned long)status);
-}
-
-/*
- * Apply EPT permissions on a normal-plane GPA range from the secure plane.
- *
- * The secure plane cannot issue the host KVM_SET_MEMORY_ATTRIBUTES ioctl,
- * so it asks KVM to do it via the KVM_HC_VBS_SET_MEM_ATTRS hypercall, which
- * KVM honours only for a higher-privilege plane. @perms carries the access
- * bits the normal plane should retain (VBS_MEM_*); KVM translates a cleared
- * write/exec bit into NO_WRITE / NO_EXEC memory attributes.
- */
-static int secmon_apply_attrs(u64 gpa, u64 size, u32 perms)
-{
- long ret;
-
- pr_debug("apply_attrs gpa=0x%llx size=0x%llx perms=%c%c%c\n",
- gpa, size,
- (perms & VBS_MEM_READ) ? 'r' : '-',
- (perms & VBS_MEM_WRITE) ? 'w' : '-',
- (perms & VBS_MEM_EXEC) ? 'x' : '-');
-
- ret = kvm_hypercall3(KVM_HC_VBS_SET_MEM_ATTRS, gpa, size, perms);
- if (ret)
- return (int)ret;
-
- return 0;
-}
-
-/* ── per-call handlers ────────────────────────────────────────────────── */
-
-static int secmon_do_protect_memory(const void *arg, u32 arg_size)
-{
- const struct vbs_protect_memory_req *r = arg;
-
- if (arg_size < sizeof(*r))
- return -EINVAL;
-
- return secmon_apply_attrs(r->gpa, r->size, r->perms);
-}
-
-static int secmon_do_seal_kernel(const void *arg, u32 arg_size)
-{
- const struct vbs_seal_kernel_req *r = arg;
- int ret;
-
- if (arg_size < sizeof(*r))
- return -EINVAL;
-
- /* Kernel text: read + execute, no write. */
- ret = secmon_apply_attrs(r->text_gpa, r->text_size,
- VBS_MEM_READ | VBS_MEM_EXEC);
- if (ret)
- return ret;
-
- /* Kernel rodata: read only, no write, no execute. */
- return secmon_apply_attrs(r->rodata_gpa, r->rodata_size,
- VBS_MEM_READ);
-}
-
-static int secmon_do_set_module_perms(const void *arg, u32 arg_size)
-{
- const struct vbs_set_module_perms_req *hdr = arg;
- const struct vbs_module_section *sec;
- u32 i, n;
-
- if (arg_size < sizeof(*hdr))
- return -EINVAL;
-
- n = hdr->nr_sections;
- if (arg_size < sizeof(*hdr) + n * sizeof(*sec))
- return -EINVAL;
-
- sec = (const struct vbs_module_section *)(hdr + 1);
- for (i = 0; i < n; i++) {
- int ret = secmon_apply_attrs(sec[i].gpa, sec[i].size,
- sec[i].perms);
- if (ret)
- return ret;
- }
-
- return 0;
-}
-
-static long secmon_dispatch(u32 call_id, const void *arg, u32 arg_size,
- u32 *resp_size)
-{
- *resp_size = 0;
-
- switch (call_id) {
- case VBS_CALL_INIT:
- case VBS_CALL_SHUTDOWN:
- return 0;
-
- case VBS_CALL_PROTECT_MEMORY:
- return secmon_do_protect_memory(arg, arg_size);
- case VBS_CALL_SEAL_KERNEL:
- return secmon_do_seal_kernel(arg, arg_size);
-
- case VBS_CALL_SET_MODULE_PERMS:
- return secmon_do_set_module_perms(arg, arg_size);
-
- /*
- * Module/kexec validation and key management are acknowledged for
- * now (mirroring the previous userspace dispatcher); real signature
- * verification runs here in a later stage.
- */
- case VBS_CALL_VALIDATE_MODULE:
- case VBS_CALL_UNLOAD_MODULE:
- case VBS_CALL_ADD_KEY:
- case VBS_CALL_REVOKE_KEY:
- case VBS_CALL_SEND_CERTS:
- case VBS_CALL_KEXEC_VALIDATE:
- case VBS_CALL_KEXEC_INVALIDATE:
- return 0;
-
- default:
- pr_warn_ratelimited("unknown call_id 0x%x\n", call_id);
- return -ENOSYS;
- }
-}
-
-/* ── monitor loop ─────────────────────────────────────────────────────── */
-
-static int secmon_monitor_fn(void *unused)
-{
- long status = 0;
-
- pr_info("secure monitor started\n");
-
- for (;;) {
- struct vbs_kvm_ca *ca;
- u64 ca_gpa;
- u32 call_id, arg_size, resp_size = 0;
-
- /* Park; resume with the next request's calling-area GPA. */
- ca_gpa = secmon_vtl_return(status);
- if (!ca_gpa) {
- status = -EINVAL;
- continue;
- }
-
- ca = memremap(ca_gpa, PAGE_SIZE, MEMREMAP_WB);
- if (!ca) {
- pr_err_ratelimited("failed to map calling area 0x%llx\n",
- ca_gpa);
- status = -EFAULT;
- continue;
- }
-
- call_id = ca->call_id;
- arg_size = ca->arg_size;
- if (arg_size > PAGE_SIZE - sizeof(*ca))
- arg_size = PAGE_SIZE - sizeof(*ca);
-
- status = secmon_dispatch(call_id, ca->buffer, arg_size,
- &resp_size);
-
- ca->status = (s32)status;
- ca->resp_size = resp_size;
-
- memunmap(ca);
- }
-
- return 0;
-}
-
-static int __init secmon_init(void)
-{
- struct task_struct *t;
-
- if (!secmon_active)
- return 0;
-
- t = kthread_run(secmon_monitor_fn, NULL, "vbs-secmon");
- if (IS_ERR(t)) {
- pr_err("failed to start secure monitor: %ld\n", PTR_ERR(t));
- return PTR_ERR(t);
- }
-
- return 0;
-}
-late_initcall(secmon_init);
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 34/42] x86/realmode: skip the sub-1M trampoline for the VBS secure plane
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (32 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 33/42] drivers/virt: rename VBS park loop to secure_monitor Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 35/42] KVM: x86: deny normal-plane access to secure-plane memory Sriram Nambakam
` (7 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
The VBS secure plane (plane >0) boots from a single high-memory region
carved out of the normal plane's address space and therefore has no
sub-1M RAM for the x86 real-mode AP trampoline. reserve_real_mode()
followed by init_real_mode() then panics with "Real mode trampoline was
not allocated".
The secure plane is uniprocessor, enters directly in long mode and never
uses the trampoline, so point x86_platform.realmode_reserve/realmode_init
at x86_init_noop for it. This mirrors how the Hyper-V VTL (hv_vtl.c) and
Xen PV ports disable the trampoline.
Gated at compile time on CONFIG_VBS_SECURE_MONITOR (only the secure-plane
kernel sets it) and at runtime on the "secure_monitor" early param (the
normal plane never passes it), so plane 0 is unaffected.
---
arch/x86/include/asm/kvm_host.h | 17 +++++--
arch/x86/kvm/mmu/mmu.c | 36 ++++++++++++++
arch/x86/kvm/mmu/spte.h | 12 +++--
arch/x86/kvm/x86.c | 85 +++++++++++++++++++++++++++++----
arch/x86/realmode/init.c | 26 ++++++++++
include/linux/kvm_host.h | 35 ++++++++++++++
include/uapi/linux/kvm.h | 1 +
virt/kvm/kvm_main.c | 6 +++
8 files changed, 202 insertions(+), 16 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index b7d478dcc1a5..bbccb9d3d801 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -380,15 +380,24 @@ union kvm_mmu_page_role {
*/
unsigned cr4_smep:1;
- unsigned:3;
+ /*
+ * Plane (privilege level) that owns this shadow page. VM
+ * planes share memslots but must have independent page
+ * tables so that a higher-privilege plane can restrict a
+ * lower plane's access (e.g. deny reads of secure-plane
+ * memory). Tagging the role keeps each plane's roots and
+ * SPTEs separate. Always 0 when CONFIG_VM_PLANES is off.
+ */
+ unsigned plane:4;
/*
* This is left at the top of the word so that
* kvm_memslots_for_spte_role can extract it with a
- * simple shift. While there is room, give it a whole
- * byte so it is also faster to load it from memory.
+ * simple shift. smm is only ever used as a boolean, so it
+ * is reduced to 7 bits (from a full byte) to make room for
+ * cr4_smep and the VM-planes plane tag above.
*/
- unsigned smm:8;
+ unsigned smm:7;
};
};
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 6e41c5df72ed..960e212c5ee3 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -4708,6 +4708,34 @@ static int kvm_mmu_faultin_pfn(struct kvm_vcpu *vcpu,
return -EFAULT;
}
+ /*
+ * A higher-privilege plane may forbid this plane from accessing a gfn
+ * (e.g. to hide secure-plane memory from the normal plane). Two cases
+ * cannot be represented as a present SPTE and must be denied outright,
+ * exiting to userspace with a memory fault rather than (re)building an
+ * SPTE the access will immediately re-fault on:
+ *
+ * - NO_READ: there is no present-but-unreadable EPT entry, so leave the
+ * gfn unmapped for this plane.
+ *
+ * - NO_WRITE on a write fault: make_spte() strips ACC_WRITE_MASK and
+ * builds a read-only SPTE, so a guest write would re-fault forever
+ * (an unresolvable EPT write-violation livelock). Deny it instead so
+ * the violation is visible and can be mediated (e.g. HEKI text_poke
+ * is routed through the secure plane rather than written directly).
+ */
+ {
+ unsigned long plane_attrs =
+ kvm_plane_access_attributes(vcpu->plane, fault->gfn);
+
+ if ((plane_attrs & KVM_MEMORY_ATTRIBUTE_NO_READ) ||
+ (fault->write &&
+ (plane_attrs & KVM_MEMORY_ATTRIBUTE_NO_WRITE))) {
+ kvm_mmu_prepare_memory_fault_exit(vcpu, fault);
+ return -EFAULT;
+ }
+ }
+
if (unlikely(!slot))
return kvm_handle_noslot_fault(vcpu, fault, access);
@@ -5884,6 +5912,14 @@ kvm_calc_tdp_mmu_root_page_role(struct kvm_vcpu *vcpu,
role.direct = true;
role.has_4_byte_gpte = false;
+ /*
+ * Give each VM plane its own TDP root. Planes share memslots but
+ * need independent page tables so a higher-privilege plane can
+ * restrict a lower plane's access to a GFN. plane_level is 0 (and
+ * thus a no-op) on non-plane VMs and when CONFIG_VM_PLANES is off.
+ */
+ role.plane = vcpu->plane_level;
+
/* All TDP pages are supervisor-executable */
role.access = ACC_ALL;
if (role.cr4_smep && shadow_user_mask)
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 144f7c5a1040..ed03bcdbf82d 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -580,9 +580,13 @@ void __init kvm_mmu_spte_module_init(void);
void kvm_mmu_reset_all_pte_masks(void);
/*
- * Apply memory protection attributes to pte_access.
- * If memory attributes have NO_WRITE or NO_EXEC set for a GFN,
- * strip the corresponding access bits before building the SPTE.
+ * Apply cross-plane access restrictions to pte_access when building an SPTE
+ * for the faulting plane. A higher-privilege plane may downgrade a lower
+ * plane's access to a GFN via its per-plane access_attr_array. NO_WRITE and
+ * NO_EXEC are enforced here by stripping the corresponding access bits.
+ * NO_READ cannot be expressed as a present-but-unreadable SPTE on EPT, so it
+ * is enforced earlier in the fault handler (kvm_mmu_faultin_pfn) by refusing
+ * to map the page.
*/
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
static inline unsigned int kvm_plane_filter_pte_access(struct kvm_vcpu *vcpu,
@@ -591,7 +595,7 @@ static inline unsigned int kvm_plane_filter_pte_access(struct kvm_vcpu *vcpu,
{
unsigned long attrs;
- attrs = kvm_get_memory_attributes(vcpu->kvm, gfn);
+ attrs = kvm_plane_access_attributes(vcpu->plane, gfn);
if (attrs & KVM_MEMORY_ATTRIBUTE_NO_WRITE)
pte_access &= ~ACC_WRITE_MASK;
if (attrs & KVM_MEMORY_ATTRIBUTE_NO_EXEC)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 3c73ab1dcfe8..c8c37d569023 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10494,6 +10494,61 @@ static int complete_hypercall_exit(struct kvm_vcpu *vcpu)
return kvm_skip_emulated_instruction(vcpu);
}
+#if defined(CONFIG_VM_PLANES) && defined(CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES)
+/*
+ * Apply cross-plane access restrictions requested by a higher-privilege plane.
+ * Stores @attrs (NO_READ/NO_WRITE/NO_EXEC) for [@start, @end) in @plane's
+ * access_attr_array and zaps the range so any pages already mapped in @plane's
+ * EPT re-fault and pick up the restriction. @attrs == 0 clears the
+ * restriction for the range.
+ */
+static int kvm_plane_set_access_attrs(struct kvm *kvm, struct kvm_plane *plane,
+ gfn_t start, gfn_t end, unsigned long attrs)
+{
+ void *entry = attrs ? xa_mk_value(attrs) : NULL;
+ gfn_t gfn;
+ int r = 0;
+
+ mutex_lock(&kvm->slots_lock);
+
+ /*
+ * Reserve slots up front so the store loop below cannot fail partway
+ * through and leave a gap (a still-readable page) in the protected
+ * range. Clearing a restriction (entry == NULL) never allocates.
+ */
+ if (entry) {
+ for (gfn = start; gfn < end; gfn++) {
+ r = xa_reserve(&plane->access_attr_array, gfn,
+ GFP_KERNEL_ACCOUNT);
+ if (r)
+ goto out_unlock;
+
+ cond_resched();
+ }
+ }
+
+ for (gfn = start; gfn < end; gfn++) {
+ r = xa_err(xa_store(&plane->access_attr_array, gfn, entry,
+ GFP_KERNEL_ACCOUNT));
+ if (KVM_BUG_ON(r, kvm))
+ goto out_unlock;
+
+ cond_resched();
+ }
+
+ /*
+ * Re-fault the affected gfns in the plane's EPT so the new restriction
+ * takes effect on existing mappings. Zapping all roots is harmless;
+ * other planes simply rebuild identical entries on next access.
+ */
+ kvm_zap_gfn_range(kvm, start, end);
+
+out_unlock:
+ mutex_unlock(&kvm->slots_lock);
+ return r;
+}
+#endif /* CONFIG_VM_PLANES && CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+
int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
int (*complete_hypercall)(struct kvm_vcpu *))
{
@@ -10683,16 +10738,21 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
case KVM_HC_VBS_SET_MEM_ATTRS:
#if defined(CONFIG_VM_PLANES) && defined(CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES)
/*
- * The secure plane (plane >0) enforces EPT permissions on the
- * normal plane's memory. It cannot issue the host
+ * The secure plane (plane >0) enforces EPT permissions on a
+ * lower plane's memory. It cannot issue the host
* KVM_SET_MEMORY_ATTRIBUTES ioctl, so it asks KVM to do it via
- * this hypercall. Only a higher-privilege plane may call it.
+ * this hypercall. Only a higher-privilege plane may call it;
+ * the restriction is applied to the plane directly below the
+ * caller.
*
* a0 = guest-physical address (page aligned)
* a1 = region size in bytes (page aligned)
- * a2 = access bits to retain for lower planes:
- * bit0 read (implicit), bit1 write, bit2 exec
- * (matches VBS_MEM_READ/WRITE/EXEC)
+ * a2 = access bits to retain for the lower plane:
+ * bit0 read, bit1 write, bit2 exec
+ * (matches VBS_MEM_READ/WRITE/EXEC). A cleared bit adds
+ * the corresponding NO_READ/NO_WRITE/NO_EXEC restriction;
+ * a2 = 0 hides the range entirely (e.g. secure-plane
+ * memory that the normal plane must not read).
*/
if (vcpu->plane_level == 0) {
ret = -KVM_EPERM;
@@ -10704,17 +10764,26 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
ret = -KVM_EINVAL;
goto out;
} else {
+ struct kvm_plane *target;
unsigned long attrs = 0;
gfn_t start = a0 >> PAGE_SHIFT;
gfn_t end = (a0 + a1) >> PAGE_SHIFT;
+ target = vcpu->kvm->planes[vcpu->plane_level - 1];
+ if (!target) {
+ ret = -KVM_EINVAL;
+ goto out;
+ }
+
+ if (!(a2 & BIT(0)))
+ attrs |= KVM_MEMORY_ATTRIBUTE_NO_READ;
if (!(a2 & BIT(1)))
attrs |= KVM_MEMORY_ATTRIBUTE_NO_WRITE;
if (!(a2 & BIT(2)))
attrs |= KVM_MEMORY_ATTRIBUTE_NO_EXEC;
- if (kvm_vm_set_mem_attributes(vcpu->kvm, start, end,
- attrs))
+ if (kvm_plane_set_access_attrs(vcpu->kvm, target, start,
+ end, attrs))
ret = -KVM_EINVAL;
else
ret = 0;
diff --git a/arch/x86/realmode/init.c b/arch/x86/realmode/init.c
index 694d80a5c68e..01855a913b10 100644
--- a/arch/x86/realmode/init.c
+++ b/arch/x86/realmode/init.c
@@ -11,6 +11,7 @@
#include <asm/crash.h>
#include <asm/msr.h>
#include <asm/sev.h>
+#include <asm/x86_init.h>
struct real_mode_header *real_mode_header;
u32 *trampoline_cr4_features;
@@ -44,6 +45,31 @@ void load_trampoline_pgtable(void)
__flush_tlb_all();
}
+#ifdef CONFIG_VBS_SECURE_MONITOR
+/*
+ * A KVM VM-planes secure plane (plane > 0) is entered directly in 64-bit long
+ * mode and boots from a single carved-out high-memory region that contains no
+ * RAM below 1 MiB. It runs uniprocessor with no firmware, ACPI sleep, or
+ * hibernation, so the 16-bit real-mode trampoline can neither be allocated
+ * (there is no sub-1M memory) nor is it ever used (no AP bringup or wakeup).
+ *
+ * Disable the real-mode setup the same way Hyper-V VTL and Xen PV do, by
+ * pointing the x86_platform real-mode hooks at the no-op handler. This is
+ * installed from an early_param so it takes effect before setup_arch() calls
+ * x86_platform.realmode_reserve(). Triggered by the "secure_monitor"
+ * command-line option, the same switch that activates the in-kernel
+ * secure-plane monitor.
+ */
+static int __init secure_plane_no_real_mode(char *arg)
+{
+ x86_platform.realmode_reserve = x86_init_noop;
+ x86_platform.realmode_init = x86_init_noop;
+ pr_info("realmode: secure plane: skipping sub-1M trampoline\n");
+ return 0;
+}
+early_param("secure_monitor", secure_plane_no_real_mode);
+#endif /* CONFIG_VBS_SECURE_MONITOR */
+
void __init reserve_real_mode(void)
{
phys_addr_t mem, limit = x86_init.resources.realmode_limit;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index f14d78fd8cd3..05c9edd4a73d 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -895,6 +895,18 @@ struct kvm_plane {
/* Per-Plane VCPU array */
struct xarray vcpu_array;
+#ifdef CONFIG_VM_PLANES
+ /*
+ * Cross-plane access restrictions imposed on THIS plane by a
+ * higher-privilege plane. Each entry holds NO_READ/NO_WRITE/NO_EXEC
+ * bits for a gfn and is enforced when building this plane's SPTEs
+ * (planes have independent EPT roots). Distinct from
+ * kvm->mem_attr_array, which holds VM-wide PRIVATE/CoCo attributes.
+ * Protected by kvm->slots_lock for writes, RCU for reads.
+ */
+ struct xarray access_attr_array;
+#endif
+
struct kvm_arch_plane arch;
};
@@ -2739,6 +2751,29 @@ static inline bool kvm_mem_is_private(struct kvm *kvm, gfn_t gfn)
}
#endif /* CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES */
+#ifdef CONFIG_VM_PLANES
+/*
+ * Cross-plane access restrictions: a higher-privilege plane downgrades a
+ * lower plane's access (NO_READ/NO_WRITE/NO_EXEC) to a gfn by storing bits in
+ * that lower plane's access_attr_array. Enforced when building the lower
+ * plane's SPTEs (planes have independent EPT roots). Returns 0 when no
+ * restriction applies.
+ */
+static inline unsigned long kvm_plane_access_attributes(struct kvm_plane *plane,
+ gfn_t gfn)
+{
+ if (!plane)
+ return 0;
+ return xa_to_value(xa_load(&plane->access_attr_array, gfn));
+}
+#else
+static inline unsigned long kvm_plane_access_attributes(struct kvm_plane *plane,
+ gfn_t gfn)
+{
+ return 0;
+}
+#endif /* CONFIG_VM_PLANES */
+
#ifdef CONFIG_KVM_GUEST_MEMFD
int kvm_gmem_get_pfn(struct kvm *kvm, struct kvm_memory_slot *slot,
gfn_t gfn, kvm_pfn_t *pfn, struct page **page,
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 348628c7b17e..3118b31d13f6 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1688,6 +1688,7 @@ struct kvm_memory_attributes {
#define KVM_MEMORY_ATTRIBUTE_PRIVATE (1ULL << 3)
#define KVM_MEMORY_ATTRIBUTE_NO_WRITE (1ULL << 4)
#define KVM_MEMORY_ATTRIBUTE_NO_EXEC (1ULL << 5)
+#define KVM_MEMORY_ATTRIBUTE_NO_READ (1ULL << 6)
#define KVM_CREATE_GUEST_MEMFD _IOWR(KVMIO, 0xd4, struct kvm_create_guest_memfd)
#define GUEST_MEMFD_FLAG_MMAP (1ULL << 0)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 553c282500fd..3a1a09f26340 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -1236,6 +1236,9 @@ static struct kvm_plane *kvm_create_plane(struct kvm *kvm, unsigned plane_level)
plane->level = plane_level;
xa_init(&plane->vcpu_array);
+#ifdef CONFIG_VM_PLANES
+ xa_init(&plane->access_attr_array);
+#endif
if (kvm_arch_plane_init(kvm, plane, plane_level))
goto out_free_plane;
@@ -1254,6 +1257,9 @@ static struct kvm_plane *kvm_create_plane(struct kvm *kvm, unsigned plane_level)
static void kvm_destroy_one_plane(struct kvm_plane *plane)
{
kvm_arch_plane_destroy(plane);
+#ifdef CONFIG_VM_PLANES
+ xa_destroy(&plane->access_attr_array);
+#endif
kvm_free_plane(plane);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 35/42] KVM: x86: deny normal-plane access to secure-plane memory
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (33 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 34/42] x86/realmode: skip the sub-1M trampoline for the VBS secure plane Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 36/42] KVM: plane: handle KVM_CHECK_EXTENSION on the plane fd Sriram Nambakam
` (6 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
VM planes share one guest physical address space (one set of memslots),
so today the normal plane (plane 0) can read the secure plane's RAM.
Add per-plane access control so a higher-privilege plane can hide its
memory from a lower one:
- Encode the plane into union kvm_mmu_page_role (the previously spare
4 bits) so each plane gets its own TDP/EPT root instead of sharing
one set of page tables.
- Give each struct kvm_plane its own access_attr_array (xarray),
independent of kvm->mem_attr_array, to avoid coupling with the
private/CoCo memory-attribute machinery.
- Add KVM_MEMORY_ATTRIBUTE_NO_READ. NO_READ cannot be expressed as a
present-but-unreadable EPT entry on all hardware, so it is enforced
in the fault path: kvm_mmu_faultin_pfn() refuses to map a NO_READ
gfn (and a write to a NO_WRITE gfn) for the faulting plane and exits
with KVM_EXIT_MEMORY_FAULT instead of building an SPTE the access
would immediately re-fault on. NO_WRITE/NO_EXEC continue to be
stripped in kvm_plane_filter_pte_access().
- KVM_HC_VBS_SET_MEM_ATTRS lets a plane >0 apply NO_READ/NO_WRITE/
NO_EXEC to the plane directly below it (the secure plane cannot issue
the host KVM_SET_MEMORY_ATTRIBUTES ioctl). a2 is an allow-mask:
bit0 read, bit1 write, bit2 exec; a cleared bit adds the matching
restriction, a2 == 0 hides the range entirely.
drivers/virt/secure_monitor.c uses this to seal the secure plane's own
RAM (walk_system_ram_range -> SET_MEM_ATTRS with perms 0) from the
normal plane before handing control back, so plane 0 can no longer read
plane 1.
---
drivers/virt/secure_monitor.c | 79 +++++++++++++++++++++++++++++++++--
1 file changed, 76 insertions(+), 3 deletions(-)
diff --git a/drivers/virt/secure_monitor.c b/drivers/virt/secure_monitor.c
index 2d181c32c439..028ae222037a 100644
--- a/drivers/virt/secure_monitor.c
+++ b/drivers/virt/secure_monitor.c
@@ -25,11 +25,15 @@
* Because all planes of a VM share the same memslots (struct kvm_plane has no
* memslots of its own; they live in struct kvm), the secure plane sees the
* same guest-physical address space as the normal plane and can read the
- * calling area and the GPAs referenced by each request directly.
+ * calling area and the GPAs referenced by each request directly. This same
+ * sharing means the secure plane must explicitly hide its own RAM from the
+ * normal plane: on startup it walks its system RAM and asks KVM (via
+ * KVM_HC_VBS_SET_MEM_ATTRS) to deny the normal plane read/write/exec access,
+ * so plane 0 cannot read secure-plane memory.
*
* For now every VTL call is acknowledged as a no-op so the normal plane can
- * make progress; the real per-call handlers (self-protection, HEKI memory
- * protection, kernel sealing, …) are plumbed in incrementally.
+ * make progress; the remaining per-call handlers (HEKI memory protection,
+ * kernel sealing, …) are plumbed in incrementally.
*
* Activated by the "secure_monitor" kernel command-line option; without it
* this kernel boots normally and never parks.
@@ -41,10 +45,13 @@
#include <linux/init.h>
#include <linux/kthread.h>
#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/memblock.h>
#include <linux/mm.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/err.h>
+#include <linux/vbs.h>
#include <linux/kvm_para.h>
#include <asm/kvm_para.h>
@@ -85,12 +92,78 @@ static u64 secmon_vtl_return(long status)
return kvm_hypercall1(KVM_HC_VBS_VTL_RETURN, (unsigned long)status);
}
+/*
+ * Apply EPT permissions on a normal-plane GPA range from the secure plane.
+ *
+ * The secure plane cannot issue the host KVM_SET_MEMORY_ATTRIBUTES ioctl, so
+ * it asks KVM to do it via the KVM_HC_VBS_SET_MEM_ATTRS hypercall, which KVM
+ * honours only for a higher-privilege plane (it applies the attributes to the
+ * plane directly below the caller). @perms carries the access bits the
+ * normal plane should retain (VBS_MEM_*); KVM translates a cleared
+ * read/write/exec bit into NO_READ / NO_WRITE / NO_EXEC. @perms == 0 hides
+ * the range entirely.
+ */
+static int secmon_apply_attrs(u64 gpa, u64 size, u32 perms)
+{
+ long ret;
+
+ pr_debug("apply_attrs gpa=0x%llx size=0x%llx perms=%c%c%c\n",
+ gpa, size,
+ (perms & VBS_MEM_READ) ? 'r' : '-',
+ (perms & VBS_MEM_WRITE) ? 'w' : '-',
+ (perms & VBS_MEM_EXEC) ? 'x' : '-');
+
+ ret = kvm_hypercall3(KVM_HC_VBS_SET_MEM_ATTRS, gpa, size, perms);
+ if (ret)
+ return (int)ret;
+
+ return 0;
+}
+
+/*
+ * Hide one range of this plane's RAM from the normal plane. perms = 0 means
+ * "retain no access" (no read/write/exec), so the normal plane faults and is
+ * denied if it tries to touch secure-plane memory.
+ */
+static int secmon_hide_range(unsigned long start_pfn, unsigned long nr_pages,
+ void *arg)
+{
+ unsigned long gpa = start_pfn << PAGE_SHIFT;
+ unsigned long size = nr_pages << PAGE_SHIFT;
+ int r;
+
+ r = secmon_apply_attrs(gpa, size, 0);
+ if (r)
+ pr_warn("failed to protect RAM [0x%lx+0x%lx]: %d\n",
+ gpa, size, r);
+ else
+ pr_info("protected RAM [0x%lx+0x%lx] from normal plane\n",
+ gpa, size);
+
+ /* Continue with the remaining ranges even if one fails. */
+ return 0;
+}
+
+/*
+ * Deny the normal plane access to all of the secure plane's own RAM. Runs
+ * while the normal plane is frozen in the KVM_RUN that switched to us, so
+ * there is no window during which the memory is both populated and still
+ * readable by the normal plane.
+ */
+static void secmon_protect_self(void)
+{
+ walk_system_ram_range(0, max_pfn, NULL, secmon_hide_range);
+}
+
static int secmon_monitor_fn(void *unused)
{
long status = 0;
pr_info("secure monitor started\n");
+ /* Seal our memory from the normal plane before handing control back. */
+ secmon_protect_self();
+
for (;;) {
struct vbs_kvm_ca *ca;
u64 ca_gpa;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 36/42] KVM: plane: handle KVM_CHECK_EXTENSION on the plane fd
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (34 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 35/42] KVM: x86: deny normal-plane access to secure-plane memory Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 37/42] KVM: selftests: run plane tests with a split IRQ chip Sriram Nambakam
` (5 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
A plane file descriptor did not implement KVM_CHECK_EXTENSION and failed
with -ENOTTY, but userspace needs to be able to query capabilities on it.
Forward the query to the plane's parent VM, except for KVM_CAP_PLANES
which returns 0 because a plane cannot host planes of its own.
---
virt/kvm/kvm_main.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 3a1a09f26340..6e4f3f3e6881 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -4946,12 +4946,21 @@ static long kvm_vcpu_compat_ioctl(struct file *filp,
}
#endif
+static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg);
+
static long __kvm_plane_ioctl(struct kvm_plane *plane, unsigned int ioctl, unsigned long arg)
{
void __user *argp = (void __user *)arg;
long r;
switch (ioctl) {
+ case KVM_CHECK_EXTENSION:
+ /* A plane cannot host planes of its own. */
+ if (arg == KVM_CAP_PLANES)
+ r = 0;
+ else
+ r = kvm_vm_ioctl_check_extension_generic(plane->kvm, arg);
+ break;
case KVM_CREATE_VCPU:
r = kvm_plane_ioctl_create_vcpu(plane, arg);
break;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 37/42] KVM: selftests: run plane tests with a split IRQ chip
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (35 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 36/42] KVM: plane: handle KVM_CHECK_EXTENSION on the plane fd Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 38/42] kvm: x86: drop obsolete kvm_cache_regs.h Sriram Nambakam
` (4 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Wire the plane selftests to actually exercise planes instead of skipping:
- Add vm_create_barebones_irqchip() and create the test VMs with a split
IRQ chip, which planes require, and query KVM_CAP_PLANES on the VM fd
(system scope always returns 1).
- Switch plane vCPU creation to KVM_CREATE_VCPU with the vCPU id, matching
the current plane ABI, and drop the removed KVM_CREATE_VCPU_PLANE,
KVM_CAP_PLANES_FPU and req_exit_planes paths from the tests and docs.
- Rename x86/plane_test.c to x86/plane_x86_test.c.
---
tools/testing/selftests/kvm/Makefile.kvm | 2 +-
.../testing/selftests/kvm/include/kvm_util.h | 12 ++
tools/testing/selftests/kvm/lib/kvm_util.c | 8 +-
tools/testing/selftests/kvm/plane_test.c | 20 ++--
.../x86/{plane_test.c => plane_x86_test.c} | 109 +++---------------
5 files changed, 45 insertions(+), 106 deletions(-)
rename tools/testing/selftests/kvm/x86/{plane_test.c => plane_x86_test.c} (58%)
diff --git a/tools/testing/selftests/kvm/Makefile.kvm b/tools/testing/selftests/kvm/Makefile.kvm
index 80933e942ecf..750350e187c8 100644
--- a/tools/testing/selftests/kvm/Makefile.kvm
+++ b/tools/testing/selftests/kvm/Makefile.kvm
@@ -102,7 +102,7 @@ TEST_GEN_PROGS_x86 += x86/nested_tdp_fault_test
TEST_GEN_PROGS_x86 += x86/nested_tsc_adjust_test
TEST_GEN_PROGS_x86 += x86/nested_tsc_scaling_test
TEST_GEN_PROGS_x86 += x86/nested_vmsave_vmload_test
-TEST_GEN_PROGS_x86 += x86/plane_test
+TEST_GEN_PROGS_x86 += x86/plane_x86_test
TEST_GEN_PROGS_x86 += x86/platform_info_test
TEST_GEN_PROGS_x86 += x86/pmu_counters_test
TEST_GEN_PROGS_x86 += x86/pmu_event_filter_test
diff --git a/tools/testing/selftests/kvm/include/kvm_util.h b/tools/testing/selftests/kvm/include/kvm_util.h
index 2ea2960f1e2d..c4726fc7b065 100644
--- a/tools/testing/selftests/kvm/include/kvm_util.h
+++ b/tools/testing/selftests/kvm/include/kvm_util.h
@@ -1070,6 +1070,18 @@ static inline struct kvm_vm *vm_create_barebones(void)
return ____vm_create(VM_SHAPE_DEFAULT);
}
+static inline struct kvm_vm *vm_create_barebones_irqchip(bool split)
+{
+ struct kvm_vm *vm = vm_create_barebones();
+
+ if (split)
+ vm_enable_cap(vm, KVM_CAP_SPLIT_IRQCHIP, 24);
+ else
+ vm_create_irqchip(vm);
+
+ return vm;
+}
+
static inline struct kvm_vm *vm_create_barebones_type(unsigned long type)
{
const struct vm_shape shape = {
diff --git a/tools/testing/selftests/kvm/lib/kvm_util.c b/tools/testing/selftests/kvm/lib/kvm_util.c
index 43a23634b4f4..ce53230b23d0 100644
--- a/tools/testing/selftests/kvm/lib/kvm_util.c
+++ b/tools/testing/selftests/kvm/lib/kvm_util.c
@@ -790,10 +790,8 @@ static void vm_vcpu_rm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
void kvm_vm_release(struct kvm_vm *vmp)
{
struct kvm_vcpu *vcpu, *tmp_vcpu;
- struct kvm_plane_vcpu *plane_vcpu, *tmp_plane_vcpu;
- struct kvm_plane *plane, *tmp_plane;
- list_for_each_entry_safe(vcpu, tmp, &vmp->vcpus, list)
+ list_for_each_entry_safe(vcpu, tmp_vcpu, &vmp->vcpus, list)
vm_vcpu_rm(vmp, vcpu);
kvm_free_fd(vmp->fd);
@@ -1366,8 +1364,8 @@ struct kvm_plane_vcpu *__vm_plane_vcpu_add(struct kvm_vcpu *vcpu, struct kvm_pla
plane_vcpu = calloc(1, sizeof(*plane_vcpu));
TEST_ASSERT(plane_vcpu != NULL, "Insufficient Memory");
- plane_vcpu->fd = __plane_ioctl(plane, KVM_CREATE_VCPU_PLANE, (void *)(unsigned long)vcpu->fd);
- TEST_ASSERT_VM_VCPU_IOCTL(plane_vcpu->fd >= 0, KVM_CREATE_VCPU_PLANE, plane_vcpu->fd, plane->vm);
+ plane_vcpu->fd = __plane_ioctl(plane, KVM_CREATE_VCPU, (void *)(unsigned long)vcpu->id);
+ TEST_ASSERT_VM_VCPU_IOCTL(plane_vcpu->fd >= 0, KVM_CREATE_VCPU, plane_vcpu->fd, plane->vm);
plane_vcpu->id = vcpu->id;
plane_vcpu->plane0 = vcpu;
diff --git a/tools/testing/selftests/kvm/plane_test.c b/tools/testing/selftests/kvm/plane_test.c
index 9cf3ab76b3cd..fd09d1f78ebe 100644
--- a/tools/testing/selftests/kvm/plane_test.c
+++ b/tools/testing/selftests/kvm/plane_test.c
@@ -21,7 +21,8 @@ void test_create_plane_errors(int max_planes)
struct kvm_vcpu *vcpu;
int planefd, plane_vcpufd;
- vm = vm_create_barebones();
+ /* Planes require an in-kernel (split) IRQ chip. */
+ vm = vm_create_barebones_irqchip(true);
vcpu = __vm_vcpu_add(vm, 0);
planefd = __vm_ioctl(vm, KVM_CREATE_PLANE, (void *)(unsigned long)0);
@@ -34,9 +35,9 @@ void test_create_plane_errors(int max_planes)
"Creating plane %d, expecting EINVAL. ret: %d, errno: %d",
max_planes, planefd, errno);
- plane_vcpufd = __vm_ioctl(vm, KVM_CREATE_VCPU_PLANE, (void *)(unsigned long)vcpu->fd);
- TEST_ASSERT(plane_vcpufd == -1 && errno == ENOTTY,
- "Creating vCPU for plane 0, expecting ENOTTY. ret: %d, errno: %d",
+ plane_vcpufd = __vm_ioctl(vm, KVM_CREATE_VCPU, (void *)(unsigned long)vcpu->id);
+ TEST_ASSERT(plane_vcpufd == -1 && errno == EEXIST,
+ "Creating existing vCPU for plane 0, expecting EEXIST. ret: %d, errno: %d",
plane_vcpufd, errno);
kvm_vm_free(vm);
@@ -50,7 +51,7 @@ void test_create_plane(void)
struct kvm_plane *plane;
int r;
- vm = vm_create_barebones();
+ vm = vm_create_barebones_irqchip(true);
vcpu = __vm_vcpu_add(vm, 0);
plane = vm_plane_add(vm, 1);
@@ -70,7 +71,7 @@ void test_create_plane(void)
__vm_plane_vcpu_add(vcpu, plane);
- r = __plane_ioctl(plane, KVM_CREATE_VCPU_PLANE, (void *)(unsigned long)vcpu->fd);
+ r = __plane_ioctl(plane, KVM_CREATE_VCPU, (void *)(unsigned long)vcpu->id);
TEST_ASSERT(r == -1 && errno == EEXIST,
"Creating vCPU again for plane 1. ret: %d, errno: %d",
r, errno);
@@ -86,7 +87,10 @@ void test_create_plane(void)
int main(int argc, char *argv[])
{
- int cap_planes = kvm_check_cap(KVM_CAP_PLANES);
+ struct kvm_vm *vm = vm_create_barebones_irqchip(true);
+ int cap_planes = vm_check_cap(vm, KVM_CAP_PLANES);
+
+ kvm_vm_free(vm);
TEST_REQUIRE(cap_planes);
ksft_print_header();
@@ -98,6 +102,8 @@ int main(int argc, char *argv[])
if (cap_planes > 1)
test_create_plane();
+ else
+ ksft_test_result_skip("plane creation requires KVM_CAP_PLANES > 1\n");
ksft_finished();
}
diff --git a/tools/testing/selftests/kvm/x86/plane_test.c b/tools/testing/selftests/kvm/x86/plane_x86_test.c
similarity index 58%
rename from tools/testing/selftests/kvm/x86/plane_test.c
rename to tools/testing/selftests/kvm/x86/plane_x86_test.c
index 0fdd8a066723..8f0919371383 100644
--- a/tools/testing/selftests/kvm/x86/plane_test.c
+++ b/tools/testing/selftests/kvm/x86/plane_x86_test.c
@@ -5,6 +5,7 @@
* Test for x86-specific VM plane functionality
*/
#include <fcntl.h>
+#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -26,7 +27,7 @@ static void test_plane_regs(void)
struct kvm_regs regs0, regs1;
- vm = vm_create_barebones();
+ vm = vm_create_barebones_irqchip(true);
vcpu = __vm_vcpu_add(vm, 0);
plane = vm_plane_add(vm, 1);
plane_vcpu = __vm_plane_vcpu_add(vcpu, plane);
@@ -62,8 +63,7 @@ static void test_plane_fpu_nonshared(void)
struct kvm_xsave xsave0, xsave1;
- vm = vm_create_barebones();
- TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_FPU), false);
+ vm = vm_create_barebones_irqchip(true);
vcpu = __vm_vcpu_add(vm, 0);
vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
@@ -93,79 +93,15 @@ static void test_plane_fpu_nonshared(void)
ksft_test_result_pass("get/set FPU not shared across planes\n");
}
-static void test_plane_fpu_shared(void)
-{
- struct kvm_vm *vm;
- struct kvm_vcpu *vcpu;
- struct kvm_plane *plane;
- struct kvm_plane_vcpu *plane_vcpu;
-
- struct kvm_xsave xsave0, xsave1;
-
- vm = vm_create_barebones();
- vm_enable_cap(vm, KVM_CAP_PLANES_FPU, 1ul);
- TEST_ASSERT_EQ(vm_check_cap(vm, KVM_CAP_PLANES_FPU), true);
-
- vcpu = __vm_vcpu_add(vm, 0);
- vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
- vcpu_set_cpuid(vcpu);
-
- plane = vm_plane_add(vm, 1);
- plane_vcpu = __vm_plane_vcpu_add(vcpu, plane);
-
- vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
-
- xsave0.region[XSTATE_BV_OFFSET] |= XFEATURE_MASK_FP | XFEATURE_MASK_SSE;
- xsave0.region[XMM_OFFSET] = 0x12345678;
- vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
- plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave1);
- TEST_ASSERT_EQ(xsave1.region[XMM_OFFSET], 0x12345678);
-
- xsave1.region[XSTATE_BV_OFFSET] |= XFEATURE_MASK_FP | XFEATURE_MASK_SSE;
- xsave1.region[XMM_OFFSET] = 0x87654321;
- plane_vcpu_ioctl(plane_vcpu, KVM_SET_XSAVE, &xsave1);
- vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
- TEST_ASSERT_EQ(xsave0.region[XMM_OFFSET], 0x87654321);
-
- ksft_test_result_pass("get/set FPU shared across planes\n");
-
- if (!this_cpu_has(X86_FEATURE_PKU)) {
- ksft_test_result_skip("get/set PKRU with shared FPU\n");
- goto exit;
- }
-
- xsave0.region[XSTATE_BV_OFFSET] = XFEATURE_MASK_PKRU;
- xsave0.region[PKRU_OFFSET] = 0xffffffff;
- vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
- plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave0);
-
- xsave0.region[XSTATE_BV_OFFSET] = XFEATURE_MASK_PKRU;
- xsave0.region[PKRU_OFFSET] = 0xaaaaaaaa;
- vcpu_ioctl(vcpu, KVM_SET_XSAVE, &xsave0);
- plane_vcpu_ioctl(plane_vcpu, KVM_GET_XSAVE, &xsave1);
- assert(xsave1.region[PKRU_OFFSET] == 0xffffffff);
-
- xsave1.region[XSTATE_BV_OFFSET] = XFEATURE_MASK_PKRU;
- xsave1.region[PKRU_OFFSET] = 0x55555555;
- plane_vcpu_ioctl(plane_vcpu, KVM_SET_XSAVE, &xsave1);
- vcpu_ioctl(vcpu, KVM_GET_XSAVE, &xsave0);
- assert(xsave0.region[PKRU_OFFSET] == 0xaaaaaaaa);
-
- ksft_test_result_pass("get/set PKRU with shared FPU\n");
-
-exit:
- kvm_vm_free(vm);
-}
-
#define APIC_SPIV 0xF0
#define APIC_IRR 0x200
#define MYVEC 192
-#define MAKE_MSI(cpu, vector) ((struct kvm_msi){ \
- .address_lo = APIC_DEFAULT_GPA + (((cpu) & 0xff) << 8), \
- .address_hi = (cpu) & ~0xff, \
- .data = (vector), \
+#define MAKE_MSI(cpu, vector) ((struct kvm_msi){ \
+ .address_lo = APIC_DEFAULT_GPA + (((cpu) & 0xff) << 8), \
+ .address_hi = (cpu) & ~0xff, \
+ .data = (vector), \
})
static bool has_irr(struct kvm_lapic_state *apic, int vector)
@@ -194,7 +130,7 @@ static void test_plane_msi(void)
struct kvm_msi msi = MAKE_MSI(0, MYVEC);
struct kvm_lapic_state lapic0, lapic1;
- vm = __vm_create(VM_SHAPE_DEFAULT, 1, 0);
+ vm = vm_create_barebones_irqchip(true);
vcpu = __vm_vcpu_add(vm, 0);
vcpu_init_cpuid(vcpu, kvm_get_supported_cpuid());
@@ -215,6 +151,7 @@ static void test_plane_msi(void)
do_enable_lapic(&lapic1);
plane_vcpu_ioctl(plane_vcpu, KVM_SET_LAPIC, &lapic1);
+ /* Deliver to plane 1 (via the plane fd); it must land only in plane 1. */
r = __plane_ioctl(plane, KVM_SIGNAL_MSI, &msi);
TEST_ASSERT(r == 1,
"Delivering interrupt to plane 1. ret: %d, errno: %d", r, errno);
@@ -224,46 +161,32 @@ static void test_plane_msi(void)
plane_vcpu_ioctl(plane_vcpu, KVM_GET_LAPIC, &lapic1);
TEST_ASSERT(has_irr(&lapic1, MYVEC), "Vector set in plane 1");
- /* req_exit_planes always has priority */
- vcpu->run->req_exit_planes = (1 << 1);
- vcpu_run(vcpu);
- TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_PLANE_EVENT);
- TEST_ASSERT_EQ(vcpu->run->plane_event.cause, KVM_PLANE_EVENT_INTERRUPT);
- TEST_ASSERT_EQ(vcpu->run->plane_event.pending_event_planes, (1 << 1));
- TEST_ASSERT_EQ(vcpu->run->plane_event.target, (1 << 1));
-
+ /* Deliver to plane 0 (via the vm fd); it must land in plane 0. */
r = __vm_ioctl(vm, KVM_SIGNAL_MSI, &msi);
TEST_ASSERT(r == 1,
"Delivering interrupt to plane 0. ret: %d, errno: %d", r, errno);
vcpu_ioctl(vcpu, KVM_GET_LAPIC, &lapic0);
TEST_ASSERT(has_irr(&lapic0, MYVEC), "Vector set in plane 0");
- /* req_exit_planes ignores current plane; current plane is cleared */
- vcpu->run->plane = 1;
- vcpu->run->req_exit_planes = (1 << 0) | (1 << 1);
- vcpu_run(vcpu);
- TEST_ASSERT_EQ(vcpu->run->exit_reason, KVM_EXIT_PLANE_EVENT);
- TEST_ASSERT_EQ(vcpu->run->plane_event.cause, KVM_PLANE_EVENT_INTERRUPT);
- TEST_ASSERT_EQ(vcpu->run->plane_event.pending_event_planes, (1 << 0));
- TEST_ASSERT_EQ(vcpu->run->plane_event.target, (1 << 0));
-
kvm_vm_free(vm);
- ksft_test_result_pass("signal MSI for planes\n");
+ ksft_test_result_pass("signal MSI routed per plane\n");
}
int main(int argc, char *argv[])
{
- int cap_planes = kvm_check_cap(KVM_CAP_PLANES);
+ struct kvm_vm *vm = vm_create_barebones_irqchip(true);
+ int cap_planes = vm_check_cap(vm, KVM_CAP_PLANES);
+
+ kvm_vm_free(vm);
TEST_REQUIRE(cap_planes && cap_planes > 1);
ksft_print_header();
- ksft_set_plan(5);
+ ksft_set_plan(3);
pr_info("# KVM_CAP_PLANES: %d\n", cap_planes);
test_plane_regs();
test_plane_fpu_nonshared();
- test_plane_fpu_shared();
test_plane_msi();
ksft_finished();
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 38/42] kvm: x86: drop obsolete kvm_cache_regs.h
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (36 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 37/42] KVM: selftests: run plane tests with a split IRQ chip Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 39/42] kvm: arch: finalize plane hooks and kvm_arch_vcpu_create signature Sriram Nambakam
` (3 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
This header was removed upstream (its GPR/CR accessors now live in
kvm_host.h); the linear cherry-pick spuriously recreated it while
resolving a conflict. Delete it to match current upstream and the
vm-planes-merged tree.
---
arch/x86/kvm/kvm_cache_regs.h | 249 ----------------------------------
1 file changed, 249 deletions(-)
delete mode 100644 arch/x86/kvm/kvm_cache_regs.h
diff --git a/arch/x86/kvm/kvm_cache_regs.h b/arch/x86/kvm/kvm_cache_regs.h
deleted file mode 100644
index 8ddb01191d6f..000000000000
--- a/arch/x86/kvm/kvm_cache_regs.h
+++ /dev/null
@@ -1,249 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-#ifndef ASM_KVM_CACHE_REGS_H
-#define ASM_KVM_CACHE_REGS_H
-
-#include <linux/kvm_host.h>
-
-#define KVM_POSSIBLE_CR0_GUEST_BITS (X86_CR0_TS | X86_CR0_WP)
-#define KVM_POSSIBLE_CR4_GUEST_BITS \
- (X86_CR4_PVI | X86_CR4_DE | X86_CR4_PCE | X86_CR4_OSFXSR \
- | X86_CR4_OSXMMEXCPT | X86_CR4_PGE | X86_CR4_TSD | X86_CR4_FSGSBASE \
- | X86_CR4_CET)
-
-#define X86_CR0_PDPTR_BITS (X86_CR0_CD | X86_CR0_NW | X86_CR0_PG)
-#define X86_CR4_TLBFLUSH_BITS (X86_CR4_PGE | X86_CR4_PCIDE | X86_CR4_PAE | X86_CR4_SMEP)
-#define X86_CR4_PDPTR_BITS (X86_CR4_PGE | X86_CR4_PSE | X86_CR4_PAE | X86_CR4_SMEP)
-
-static_assert(!(KVM_POSSIBLE_CR0_GUEST_BITS & X86_CR0_PDPTR_BITS));
-
-#define BUILD_KVM_GPR_ACCESSORS(lname, uname) \
-static __always_inline unsigned long kvm_##lname##_read(struct kvm_vcpu *vcpu)\
-{ \
- return vcpu->arch.regs[VCPU_REGS_##uname]; \
-} \
-static __always_inline void kvm_##lname##_write(struct kvm_vcpu *vcpu, \
- unsigned long val) \
-{ \
- vcpu->arch.regs[VCPU_REGS_##uname] = val; \
-}
-BUILD_KVM_GPR_ACCESSORS(rax, RAX)
-BUILD_KVM_GPR_ACCESSORS(rbx, RBX)
-BUILD_KVM_GPR_ACCESSORS(rcx, RCX)
-BUILD_KVM_GPR_ACCESSORS(rdx, RDX)
-BUILD_KVM_GPR_ACCESSORS(rbp, RBP)
-BUILD_KVM_GPR_ACCESSORS(rsi, RSI)
-BUILD_KVM_GPR_ACCESSORS(rdi, RDI)
-#ifdef CONFIG_X86_64
-BUILD_KVM_GPR_ACCESSORS(r8, R8)
-BUILD_KVM_GPR_ACCESSORS(r9, R9)
-BUILD_KVM_GPR_ACCESSORS(r10, R10)
-BUILD_KVM_GPR_ACCESSORS(r11, R11)
-BUILD_KVM_GPR_ACCESSORS(r12, R12)
-BUILD_KVM_GPR_ACCESSORS(r13, R13)
-BUILD_KVM_GPR_ACCESSORS(r14, R14)
-BUILD_KVM_GPR_ACCESSORS(r15, R15)
-#endif
-
-/*
- * Using the register cache from interrupt context is generally not allowed, as
- * caching a register and marking it available/dirty can't be done atomically,
- * i.e. accesses from interrupt context may clobber state or read stale data if
- * the vCPU task is in the process of updating the cache. The exception is if
- * KVM is handling a PMI IRQ/NMI VM-Exit, as that bound code sequence doesn't
- * touch the cache, it runs after the cache is reset (post VM-Exit), and PMIs
- * need to access several registers that are cacheable.
- */
-#define kvm_assert_register_caching_allowed(vcpu) \
- lockdep_assert_once(in_task() || kvm_arch_pmi_in_guest(vcpu))
-
-/*
- * avail dirty
- * 0 0 register in VMCS/VMCB
- * 0 1 *INVALID*
- * 1 0 register in vcpu->arch
- * 1 1 register in vcpu->arch, needs to be stored back
- */
-static inline bool kvm_register_is_available(struct kvm_vcpu *vcpu,
- enum kvm_reg reg)
-{
- kvm_assert_register_caching_allowed(vcpu);
- return test_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
-}
-
-static inline bool kvm_register_is_dirty(struct kvm_vcpu *vcpu,
- enum kvm_reg reg)
-{
- kvm_assert_register_caching_allowed(vcpu);
- return test_bit(reg, (unsigned long *)&vcpu->arch.regs_dirty);
-}
-
-static inline void kvm_register_mark_available(struct kvm_vcpu *vcpu,
- enum kvm_reg reg)
-{
- kvm_assert_register_caching_allowed(vcpu);
- __set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
-}
-
-static inline void kvm_register_mark_dirty(struct kvm_vcpu *vcpu,
- enum kvm_reg reg)
-{
- kvm_assert_register_caching_allowed(vcpu);
- __set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
- __set_bit(reg, (unsigned long *)&vcpu->arch.regs_dirty);
-}
-
-/*
- * kvm_register_test_and_mark_available() is a special snowflake that uses an
- * arch bitop directly to avoid the explicit instrumentation that comes with
- * the generic bitops. This allows code that cannot be instrumented (noinstr
- * functions), e.g. the low level VM-Enter/VM-Exit paths, to cache registers.
- */
-static __always_inline bool kvm_register_test_and_mark_available(struct kvm_vcpu *vcpu,
- enum kvm_reg reg)
-{
- kvm_assert_register_caching_allowed(vcpu);
- return arch___test_and_set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
-}
-
-/*
- * The "raw" register helpers are only for cases where the full 64 bits of a
- * register are read/written irrespective of current vCPU mode. In other words,
- * odds are good you shouldn't be using the raw variants.
- */
-static inline unsigned long kvm_register_read_raw(struct kvm_vcpu *vcpu, int reg)
-{
- if (WARN_ON_ONCE((unsigned int)reg >= NR_VCPU_REGS))
- return 0;
-
- if (!kvm_register_is_available(vcpu, reg))
- kvm_x86_call(cache_reg)(vcpu, reg);
-
- return vcpu->arch.regs[reg];
-}
-
-static inline void kvm_register_write_raw(struct kvm_vcpu *vcpu, int reg,
- unsigned long val)
-{
- if (WARN_ON_ONCE((unsigned int)reg >= NR_VCPU_REGS))
- return;
-
- vcpu->arch.regs[reg] = val;
- kvm_register_mark_dirty(vcpu, reg);
-}
-
-static inline unsigned long kvm_rip_read(struct kvm_vcpu *vcpu)
-{
- return kvm_register_read_raw(vcpu, VCPU_REGS_RIP);
-}
-
-static inline void kvm_rip_write(struct kvm_vcpu *vcpu, unsigned long val)
-{
- kvm_register_write_raw(vcpu, VCPU_REGS_RIP, val);
-}
-
-static inline unsigned long kvm_rsp_read(struct kvm_vcpu *vcpu)
-{
- return kvm_register_read_raw(vcpu, VCPU_REGS_RSP);
-}
-
-static inline void kvm_rsp_write(struct kvm_vcpu *vcpu, unsigned long val)
-{
- kvm_register_write_raw(vcpu, VCPU_REGS_RSP, val);
-}
-
-static inline u64 kvm_pdptr_read(struct kvm_vcpu *vcpu, int index)
-{
- might_sleep(); /* on svm */
-
- if (!kvm_register_is_available(vcpu, VCPU_EXREG_PDPTR))
- kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_PDPTR);
-
- return vcpu->arch.walk_mmu->pdptrs[index];
-}
-
-static inline void kvm_pdptr_write(struct kvm_vcpu *vcpu, int index, u64 value)
-{
- vcpu->arch.walk_mmu->pdptrs[index] = value;
-}
-
-static inline ulong kvm_read_cr0_bits(struct kvm_vcpu *vcpu, ulong mask)
-{
- ulong tmask = mask & KVM_POSSIBLE_CR0_GUEST_BITS;
- if ((tmask & vcpu->arch.cr0_guest_owned_bits) &&
- !kvm_register_is_available(vcpu, VCPU_EXREG_CR0))
- kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_CR0);
- return vcpu->arch.cr0 & mask;
-}
-
-static __always_inline bool kvm_is_cr0_bit_set(struct kvm_vcpu *vcpu,
- unsigned long cr0_bit)
-{
- BUILD_BUG_ON(!is_power_of_2(cr0_bit));
-
- return !!kvm_read_cr0_bits(vcpu, cr0_bit);
-}
-
-static inline ulong kvm_read_cr0(struct kvm_vcpu *vcpu)
-{
- return kvm_read_cr0_bits(vcpu, ~0UL);
-}
-
-static inline ulong kvm_read_cr4_bits(struct kvm_vcpu *vcpu, ulong mask)
-{
- ulong tmask = mask & KVM_POSSIBLE_CR4_GUEST_BITS;
- if ((tmask & vcpu->arch.cr4_guest_owned_bits) &&
- !kvm_register_is_available(vcpu, VCPU_EXREG_CR4))
- kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_CR4);
- return vcpu->arch.cr4 & mask;
-}
-
-static __always_inline bool kvm_is_cr4_bit_set(struct kvm_vcpu *vcpu,
- unsigned long cr4_bit)
-{
- BUILD_BUG_ON(!is_power_of_2(cr4_bit));
-
- return !!kvm_read_cr4_bits(vcpu, cr4_bit);
-}
-
-static inline ulong kvm_read_cr3(struct kvm_vcpu *vcpu)
-{
- if (!kvm_register_is_available(vcpu, VCPU_EXREG_CR3))
- kvm_x86_call(cache_reg)(vcpu, VCPU_EXREG_CR3);
- return vcpu->arch.cr3;
-}
-
-static inline ulong kvm_read_cr4(struct kvm_vcpu *vcpu)
-{
- return kvm_read_cr4_bits(vcpu, ~0UL);
-}
-
-static inline u64 kvm_read_edx_eax(struct kvm_vcpu *vcpu)
-{
- return (kvm_rax_read(vcpu) & -1u)
- | ((u64)(kvm_rdx_read(vcpu) & -1u) << 32);
-}
-
-static inline void enter_guest_mode(struct kvm_vcpu *vcpu)
-{
- vcpu->arch.hflags |= HF_GUEST_MASK;
- vcpu->stat.guest_mode = 1;
-}
-
-static inline void leave_guest_mode(struct kvm_vcpu *vcpu)
-{
- vcpu->arch.hflags &= ~HF_GUEST_MASK;
-
- if (vcpu->arch.load_eoi_exitmap_pending) {
- vcpu->arch.load_eoi_exitmap_pending = false;
- kvm_make_request(KVM_REQ_LOAD_EOI_EXITMAP, vcpu);
- }
-
- vcpu->stat.guest_mode = 0;
-}
-
-static inline bool is_guest_mode(struct kvm_vcpu *vcpu)
-{
- return vcpu->arch.hflags & HF_GUEST_MASK;
-}
-
-#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 39/42] kvm: arch: finalize plane hooks and kvm_arch_vcpu_create signature
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (37 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 38/42] kvm: x86: drop obsolete kvm_cache_regs.h Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 40/42] kvm: x86: use kvm_vcpu scheduling-state accessors and struct stat fields Sriram Nambakam
` (2 subsequent siblings)
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Match the vm-planes-merged tree on non-x86 architectures: revert
kvm_arch_vcpu_create() to its plane-less prototype (planes attach the
vCPU to plane 0 in generic code) and add the kvm_arch_init_plane/
kvm_arch_free_plane/kvm_arch_sync_events stubs without the stray
conflict markers that the integration branch left committed.
---
arch/arm64/include/asm/kvm_host.h | 7 +++++++
arch/arm64/kvm/arm.c | 2 +-
arch/mips/include/asm/kvm_host.h | 6 ++++++
arch/powerpc/include/asm/kvm_host.h | 6 ++++++
arch/riscv/kvm/vcpu.c | 2 +-
arch/s390/include/asm/kvm_host.h | 6 ++++++
6 files changed, 27 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index d3807b6535cc..75b4e52c4bac 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -237,6 +237,9 @@ struct kvm_s2_mmu {
struct kvm_arch_memory_slot {
};
+struct kvm_arch_plane {
+};
+
/**
* struct kvm_smccc_features: Descriptor of the hypercall services exposed to the guests
*
@@ -1441,6 +1444,10 @@ static inline bool kvm_system_needs_idmapped_vectors(void)
return cpus_have_final_cap(ARM64_SPECTRE_V3A);
}
+static inline void kvm_arch_init_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_free_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_sync_events(struct kvm *kvm) {}
+
void kvm_init_host_debug_data(void);
void kvm_debug_init_vhe(void);
void kvm_vcpu_load_debug(struct kvm_vcpu *vcpu);
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index d55be436bf16..c02e19fe2b48 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -538,7 +538,7 @@ int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id)
return 0;
}
-int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu, struct kvm_plane *plane)
+int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
{
int err;
diff --git a/arch/mips/include/asm/kvm_host.h b/arch/mips/include/asm/kvm_host.h
index c48bca79207b..8dc465dd9ea8 100644
--- a/arch/mips/include/asm/kvm_host.h
+++ b/arch/mips/include/asm/kvm_host.h
@@ -147,6 +147,9 @@ struct kvm_vcpu_stat {
struct kvm_arch_memory_slot {
};
+struct kvm_arch_plane {
+};
+
#ifdef CONFIG_CPU_LOONGSON64
struct ipi_state {
uint32_t status;
@@ -903,6 +906,9 @@ extern unsigned long kvm_mips_get_ramsize(struct kvm *kvm);
extern int kvm_vcpu_ioctl_interrupt(struct kvm_vcpu *vcpu,
struct kvm_mips_interrupt *irq);
+static inline void kvm_arch_init_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_free_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_sync_events(struct kvm *kvm) {}
static inline void kvm_arch_free_memslot(struct kvm *kvm,
struct kvm_memory_slot *slot) {}
static inline void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) {}
diff --git a/arch/powerpc/include/asm/kvm_host.h b/arch/powerpc/include/asm/kvm_host.h
index 47d9900c4f85..5d7036888933 100644
--- a/arch/powerpc/include/asm/kvm_host.h
+++ b/arch/powerpc/include/asm/kvm_host.h
@@ -256,6 +256,9 @@ struct kvm_arch_memory_slot {
#endif /* CONFIG_KVM_BOOK3S_HV_POSSIBLE */
};
+struct kvm_arch_plane {
+};
+
struct kvm_hpt_info {
/* Host virtual (linear mapping) address of guest HPT */
unsigned long virt;
@@ -919,6 +922,9 @@ struct kvm_vcpu_arch {
#define __KVM_HAVE_ARCH_WQP
#define __KVM_HAVE_CREATE_DEVICE
+static inline void kvm_arch_init_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_free_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_sync_events(struct kvm *kvm) {}
static inline void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) {}
static inline void kvm_arch_flush_shadow_all(struct kvm *kvm) {}
static inline void kvm_arch_vcpu_blocking(struct kvm_vcpu *vcpu) {}
diff --git a/arch/riscv/kvm/vcpu.c b/arch/riscv/kvm/vcpu.c
index f618cd83d19a..4d7ee4059758 100644
--- a/arch/riscv/kvm/vcpu.c
+++ b/arch/riscv/kvm/vcpu.c
@@ -129,7 +129,7 @@ int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id)
return 0;
}
-int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu, struct kvm_plane *plane)
+int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
{
int rc;
diff --git a/arch/s390/include/asm/kvm_host.h b/arch/s390/include/asm/kvm_host.h
index 15c831304b51..423185a09de6 100644
--- a/arch/s390/include/asm/kvm_host.h
+++ b/arch/s390/include/asm/kvm_host.h
@@ -476,6 +476,9 @@ struct kvm_vm_stat {
struct kvm_arch_memory_slot {
};
+struct kvm_arch_plane {
+};
+
struct s390_map_info {
struct list_head list;
__u64 guest_addr;
@@ -773,6 +776,9 @@ extern int kvm_s390_gisc_unregister(struct kvm *kvm, u32 gisc);
bool kvm_s390_is_gpa_in_memslot(struct kvm *kvm, gpa_t gpa);
+static inline void kvm_arch_init_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_free_plane(struct kvm_plane *plane) {}
+static inline void kvm_arch_sync_events(struct kvm *kvm) {}
static inline void kvm_arch_free_memslot(struct kvm *kvm,
struct kvm_memory_slot *slot) {}
static inline void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) {}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 40/42] kvm: x86: use kvm_vcpu scheduling-state accessors and struct stat fields
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (38 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 39/42] kvm: arch: finalize plane hooks and kvm_arch_vcpu_create signature Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 41/42] kvm: x86: finalize per-plane APIC state and CPUID placement Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 42/42] kvm: planes: reconcile core plane state, UAPI and hypercall exit Sriram Nambakam
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Finish the conversion begun by the kvm_vcpu_common rework: read
wants_to_run through kvm_vcpu_wants_to_run() and account statistics via
the embedded vcpu->stat.* fields (not the removed vcpu->stat-> pointer)
in the SVM and nested-VMX paths.
---
arch/x86/kvm/svm/svm.c | 4 ++--
arch/x86/kvm/vmx/nested.c | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 73d80f442a49..44abbd629a66 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -2816,7 +2816,7 @@ static bool svm_pat_accesses_gpat(struct kvm_vcpu *vcpu, bool from_host)
* KVM_GET/SET_NESTED_STATE are independent of each other and can
* be ordered arbitrarily during save and restore.
*/
- WARN_ON_ONCE(from_host && vcpu->wants_to_run);
+ WARN_ON_ONCE(from_host && kvm_vcpu_wants_to_run(vcpu));
return !from_host && is_guest_mode(vcpu) && l2_has_separate_pat(vcpu);
}
@@ -3240,7 +3240,7 @@ static int interrupt_window_interception(struct kvm_vcpu *vcpu)
kvm_make_request(KVM_REQ_EVENT, vcpu);
svm_clear_vintr(to_svm(vcpu));
- ++vcpu->stat->irq_window_exits;
+ ++vcpu->stat.irq_window_exits;
return 1;
}
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index ddf6df7bee93..2f504fa2e09a 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -619,7 +619,7 @@ static int nested_vmx_check_tpr_shadow_controls(struct kvm_vcpu *vcpu,
* and only perform the check when in KVM_RUN, to avoid a false failure
* if userspace hasn't yet configured memslots during state restore.
*/
- if (warn_on_missed_cc && vcpu->wants_to_run &&
+ if (warn_on_missed_cc && kvm_vcpu_wants_to_run(vcpu) &&
nested_cpu_has(vmcs12, CPU_BASED_TPR_SHADOW) &&
!nested_cpu_has_vid(vmcs12) &&
!nested_cpu_has2(vmcs12, SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES) &&
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 41/42] kvm: x86: finalize per-plane APIC state and CPUID placement
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (39 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 40/42] kvm: x86: use kvm_vcpu scheduling-state accessors and struct stat fields Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 42/42] kvm: planes: reconcile core plane state, UAPI and hypercall exit Sriram Nambakam
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Adopt the vm-planes-merged design for x86 plane state: keep CPUID and
cpu_caps in kvm_vcpu_arch_common, make apic_map and APICv-inhibit
tracking VM-scoped again, and drop the superseded intermediate fields
(planes_share_fpu, irr_pending_planes, kvm_arch_plane.apicv_inhibit_reasons,
kvm_lapic_irq.plane). These changes originated in merge-commit conflict
resolutions that a --no-merges linearization could not carry over.
---
arch/x86/include/asm/kvm_host.h | 36 +++++-----------------
arch/x86/kvm/cpuid.c | 19 +-----------
arch/x86/kvm/hyperv.c | 1 -
arch/x86/kvm/i8254.c | 4 +--
arch/x86/kvm/lapic.c | 53 +++++++--------------------------
arch/x86/kvm/xen.c | 1 -
6 files changed, 20 insertions(+), 94 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index bbccb9d3d801..b1a7e4ca8870 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -956,12 +956,6 @@ struct kvm_vcpu_arch {
u64 ia32_xss;
u64 guest_supported_xss;
- /*
- * Only valid in plane0. The bitmask of planes that received
- * an interrupt, to be checked against req_exit_planes.
- */
- atomic_t irr_pending_planes;
-
struct kvm_pio_request pio;
void *pio_data;
void *sev_pio_data;
@@ -1175,10 +1169,6 @@ struct kvm_arch_memory_slot {
unsigned short *gfn_write_track;
};
-struct kvm_arch_plane {
- unsigned long apicv_inhibit_reasons;
-};
-
/*
* Track the mode of the optimized logical map, as the rules for decoding the
* destination vary per mode. Enabling the optimized logical map requires all
@@ -1397,13 +1387,11 @@ enum kvm_apicv_inhibit {
/*
* PIT (i8254) 're-inject' mode, relies on EOI intercept,
* which AVIC doesn't support for edge triggered interrupts.
- * Applied only to plane 0.
*/
APICV_INHIBIT_REASON_PIT_REINJ,
/*
- * AVIC is disabled because SEV doesn't support it. Sticky and applied
- * only to plane 0.
+ * AVIC is disabled because SEV doesn't support it.
*/
APICV_INHIBIT_REASON_SEV,
@@ -1483,7 +1471,6 @@ struct kvm_arch {
unsigned int indirect_shadow_pages;
u8 mmu_valid_gen;
u8 vm_type;
- bool planes_share_fpu;
bool has_private_mem;
bool has_protected_state;
bool has_protected_eoi;
@@ -1805,7 +1792,6 @@ struct kvm_lapic_irq {
u16 delivery_mode;
u16 dest_mode;
bool level;
- u8 plane;
u16 trig_mode;
u32 shorthand;
u32 dest_id;
@@ -2399,21 +2385,21 @@ gpa_t kvm_mmu_gva_to_gpa_system(struct kvm_vcpu *vcpu, gva_t gva,
bool kvm_apicv_activated(struct kvm *kvm);
bool kvm_vcpu_apicv_activated(struct kvm_vcpu *vcpu);
void __kvm_vcpu_update_apicv(struct kvm_vcpu *vcpu);
-void __kvm_set_or_clear_apicv_inhibit(struct kvm_plane *plane,
+void __kvm_set_or_clear_apicv_inhibit(struct kvm *kvm,
enum kvm_apicv_inhibit reason, bool set);
-void kvm_set_or_clear_apicv_inhibit(struct kvm_plane *plane,
+void kvm_set_or_clear_apicv_inhibit(struct kvm *kvm,
enum kvm_apicv_inhibit reason, bool set);
-static inline void kvm_set_apicv_inhibit(struct kvm_plane *plane,
+static inline void kvm_set_apicv_inhibit(struct kvm *kvm,
enum kvm_apicv_inhibit reason)
{
- kvm_set_or_clear_apicv_inhibit(plane, reason, true);
+ kvm_set_or_clear_apicv_inhibit(kvm, reason, true);
}
-static inline void kvm_clear_apicv_inhibit(struct kvm_plane *plane,
+static inline void kvm_clear_apicv_inhibit(struct kvm *kvm,
enum kvm_apicv_inhibit reason)
{
- kvm_set_or_clear_apicv_inhibit(plane, reason, false);
+ kvm_set_or_clear_apicv_inhibit(kvm, reason, false);
}
void kvm_inc_or_dec_irq_window_inhibit(struct kvm *kvm, bool inc);
@@ -2503,8 +2489,6 @@ enum {
# define kvm_memslots_for_spte_role(kvm, role) __kvm_memslots(kvm, 0)
#endif
-#define KVM_MAX_VCPU_PLANES 16
-
int kvm_cpu_has_injectable_intr(struct kvm_vcpu *v);
int kvm_cpu_has_interrupt(struct kvm_vcpu *vcpu);
int kvm_cpu_has_extint(struct kvm_vcpu *v);
@@ -2539,9 +2523,6 @@ void kvm_make_scan_ioapic_request(struct kvm *kvm);
void kvm_make_scan_ioapic_request_mask(struct kvm *kvm,
unsigned long *vcpu_bitmap);
-void kvm_arch_init_plane(struct kvm_plane *plane);
-void kvm_arch_free_plane(struct kvm_plane *plane);
-
bool kvm_arch_async_page_not_present(struct kvm_vcpu *vcpu,
struct kvm_async_pf *work);
void kvm_arch_async_page_present(struct kvm_vcpu *vcpu,
@@ -2612,7 +2593,4 @@ static inline bool kvm_arch_has_irq_bypass(void)
return enable_device_posted_irqs;
}
-int kvm_arch_nr_vcpu_planes(struct kvm *kvm);
-bool kvm_arch_planes_share_fpu(struct kvm *kvm);
-
#endif /* _ASM_X86_KVM_HOST_H */
diff --git a/arch/x86/kvm/cpuid.c b/arch/x86/kvm/cpuid.c
index ce337c6d3bcf..7b8cd379ba9f 100644
--- a/arch/x86/kvm/cpuid.c
+++ b/arch/x86/kvm/cpuid.c
@@ -555,7 +555,7 @@ static int kvm_set_cpuid(struct kvm_vcpu *vcpu, struct kvm_cpuid_entry2 *e2,
* KVM_SET_CPUID{,2} again. To support this legacy behavior, check
* whether the supplied CPUID data is equal to what's already set.
*/
- if (!kvm_can_set_cpuid_and_feature_msrs(vcpu) || vcpu->has_planes) {
+ if (!kvm_can_set_cpuid_and_feature_msrs(vcpu)) {
r = kvm_cpuid_check_equal(vcpu, e2, nent);
if (r)
goto err;
@@ -594,23 +594,6 @@ static int kvm_set_cpuid(struct kvm_vcpu *vcpu, struct kvm_cpuid_entry2 *e2,
return r;
}
-int kvm_dup_cpuid(struct kvm_vcpu *vcpu, struct kvm_vcpu *source)
-{
- if (WARN_ON_ONCE(vcpu->arch.cpuid_entries || vcpu->arch.cpuid_nent))
- return -EEXIST;
-
- vcpu->arch.cpuid_entries = kmemdup(source->arch.cpuid_entries,
- source->arch.cpuid_nent * sizeof(struct kvm_cpuid_entry2),
- GFP_KERNEL_ACCOUNT);
- if (!vcpu->arch.cpuid_entries)
- return -ENOMEM;
-
- memcpy(vcpu->arch.cpu_caps, source->arch.cpu_caps, sizeof(source->arch.cpu_caps));
- vcpu->arch.cpuid_nent = source->arch.cpuid_nent;
-
- return 0;
-}
-
/* when an old userspace process fills a new kernel module */
int kvm_vcpu_ioctl_set_cpuid(struct kvm_vcpu *vcpu,
struct kvm_cpuid *cpuid,
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 8ef09b8125b7..ee6b32d2a5cb 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -491,7 +491,6 @@ static int synic_set_irq(struct kvm_vcpu_hv_synic *synic, u32 sint)
irq.delivery_mode = APIC_DM_FIXED;
irq.vector = vector;
irq.level = 1;
-
ret = kvm_irq_delivery_to_apic(vcpu->plane, vcpu->arch.apic, &irq);
trace_kvm_hv_synic_set_irq(vcpu->vcpu_id, sint, irq.vector, ret);
return ret;
diff --git a/arch/x86/kvm/i8254.c b/arch/x86/kvm/i8254.c
index cd47fd88c9f7..bfe590378bd2 100644
--- a/arch/x86/kvm/i8254.c
+++ b/arch/x86/kvm/i8254.c
@@ -305,13 +305,13 @@ static void kvm_pit_set_reinject(struct kvm_pit *pit, bool reinject)
* So, deactivate APICv when PIT is in reinject mode.
*/
if (reinject) {
- kvm_set_apicv_inhibit(kvm->planes[0], APICV_INHIBIT_REASON_PIT_REINJ);
+ kvm_set_apicv_inhibit(kvm, APICV_INHIBIT_REASON_PIT_REINJ);
/* The initial state is preserved while ps->reinject == 0. */
kvm_pit_reset_reinject(pit);
kvm_register_irq_ack_notifier(kvm, &ps->irq_ack_notifier);
kvm_register_irq_mask_notifier(kvm, 0, &pit->mask_notifier);
} else {
- kvm_clear_apicv_inhibit(kvm->planes[0], APICV_INHIBIT_REASON_PIT_REINJ);
+ kvm_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_PIT_REINJ);
kvm_unregister_irq_ack_notifier(kvm, &ps->irq_ack_notifier);
kvm_unregister_irq_mask_notifier(kvm, 0, &pit->mask_notifier);
}
diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
index 4cca1ea6a16e..ff923133a834 100644
--- a/arch/x86/kvm/lapic.c
+++ b/arch/x86/kvm/lapic.c
@@ -405,7 +405,6 @@ enum {
static void kvm_recalculate_apic_map(struct kvm_plane *plane)
{
- struct kvm_plane *plane = kvm->planes[0];
struct kvm_apic_map *new, *old = NULL;
struct kvm *kvm = plane->kvm;
struct kvm_vcpu *vcpu;
@@ -486,19 +485,19 @@ static void kvm_recalculate_apic_map(struct kvm_plane *plane)
* map also applies to APICv.
*/
if (!new)
- kvm_set_apicv_inhibit(plane, APICV_INHIBIT_REASON_PHYSICAL_ID_ALIASED);
+ kvm_set_apicv_inhibit(kvm, APICV_INHIBIT_REASON_PHYSICAL_ID_ALIASED);
else
- kvm_clear_apicv_inhibit(plane, APICV_INHIBIT_REASON_PHYSICAL_ID_ALIASED);
+ kvm_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_PHYSICAL_ID_ALIASED);
if (!new || new->logical_mode == KVM_APIC_MODE_MAP_DISABLED)
- kvm_set_apicv_inhibit(plane, APICV_INHIBIT_REASON_LOGICAL_ID_ALIASED);
+ kvm_set_apicv_inhibit(kvm, APICV_INHIBIT_REASON_LOGICAL_ID_ALIASED);
else
- kvm_clear_apicv_inhibit(plane, APICV_INHIBIT_REASON_LOGICAL_ID_ALIASED);
+ kvm_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_LOGICAL_ID_ALIASED);
if (xapic_id_mismatch)
- kvm_set_apicv_inhibit(plane, APICV_INHIBIT_REASON_APIC_ID_MODIFIED);
+ kvm_set_apicv_inhibit(kvm, APICV_INHIBIT_REASON_APIC_ID_MODIFIED);
else
- kvm_clear_apicv_inhibit(plane, APICV_INHIBIT_REASON_APIC_ID_MODIFIED);
+ kvm_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_APIC_ID_MODIFIED);
old = rcu_dereference_protected(plane->arch.apic_map,
lockdep_is_held(&plane->arch.apic_map_lock));
@@ -1396,39 +1395,6 @@ int __kvm_irq_delivery_to_apic(struct kvm_plane *plane, struct kvm_lapic *src,
return r;
}
-static void kvm_lapic_deliver_interrupt(struct kvm_vcpu *vcpu, struct kvm_lapic *apic,
- int delivery_mode, int trig_mode, int vector)
-{
- struct kvm_vcpu *plane0_vcpu = vcpu->plane0;
- struct kvm_plane *running_plane;
- u16 req_exit_planes;
-
- kvm_x86_call(deliver_interrupt)(apic, delivery_mode, trig_mode, vector);
-
- /*
- * test_and_set_bit implies a memory barrier, so IRR is written before
- * reading irr_pending_planes below...
- */
- if (!test_and_set_bit(vcpu->plane, &plane0_vcpu->arch.irr_pending_planes)) {
- /*
- * ... and also running_plane and req_exit_planes are read after writing
- * irr_pending_planes. Both barriers pair with kvm_arch_vcpu_ioctl_run().
- */
- smp_mb__after_atomic();
-
- running_plane = READ_ONCE(plane0_vcpu->running_plane);
- if (!running_plane)
- return;
-
- req_exit_planes = READ_ONCE(plane0_vcpu->req_exit_planes);
- if (!(req_exit_planes & BIT(vcpu->plane)))
- return;
-
- kvm_make_request(KVM_REQ_PLANE_INTERRUPT,
- kvm_get_plane_vcpu(running_plane, vcpu->vcpu_id));
- }
-}
-
/*
* Add a pending IRQ into lapic.
* Return 1 if successfully added and 0 if discarded.
@@ -1470,7 +1436,8 @@ static int __apic_accept_irq(struct kvm_lapic *apic, int delivery_mode,
apic_clear_vector(vector, apic->regs + APIC_TMR);
}
- kvm_lapic_deliver_interrupt(vcpu, apic, delivery_mode, trig_mode, vector);
+ kvm_x86_call(deliver_interrupt)(apic, delivery_mode,
+ trig_mode, vector);
break;
case APIC_DM_REMRD:
@@ -2087,7 +2054,7 @@ static void apic_timer_expired(struct kvm_lapic *apic, bool from_timer_fn)
if (apic_lvtt_tscdeadline(apic) || ktimer->hv_timer_in_use)
ktimer->expired_tscdeadline = ktimer->tscdeadline;
- if (!from_timer_fn && apic->apicv_active && vcpu->wants_to_run) {
+ if (!from_timer_fn && apic->apicv_active && kvm_vcpu_wants_to_run(vcpu)) {
WARN_ON(kvm_get_running_vcpu() != vcpu);
kvm_apic_inject_pending_timer_irqs(apic);
return;
@@ -2867,7 +2834,7 @@ static void __kvm_apic_set_base(struct kvm_vcpu *vcpu, u64 value)
if ((value & MSR_IA32_APICBASE_ENABLE) &&
apic->base_address != APIC_DEFAULT_PHYS_BASE) {
- kvm_set_apicv_inhibit(vcpu_to_plane(vcpu),
+ kvm_set_apicv_inhibit(apic->vcpu->kvm,
APICV_INHIBIT_REASON_APIC_BASE_MODIFIED);
}
}
diff --git a/arch/x86/kvm/xen.c b/arch/x86/kvm/xen.c
index 399406752108..4527f04c6617 100644
--- a/arch/x86/kvm/xen.c
+++ b/arch/x86/kvm/xen.c
@@ -625,7 +625,6 @@ void kvm_xen_inject_vcpu_vector(struct kvm_vcpu *v)
irq.shorthand = APIC_DEST_NOSHORT;
irq.delivery_mode = APIC_DM_FIXED;
irq.level = 1;
-
kvm_irq_delivery_to_apic(v->plane, NULL, &irq);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [RFC PATCH v1 42/42] kvm: planes: reconcile core plane state, UAPI and hypercall exit
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
` (40 preceding siblings ...)
2026-08-05 11:03 ` [RFC PATCH v1 41/42] kvm: x86: finalize per-plane APIC state and CPUID placement Sriram Nambakam
@ 2026-08-05 11:03 ` Sriram Nambakam
41 siblings, 0 replies; 43+ messages in thread
From: Sriram Nambakam @ 2026-08-05 11:03 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Align the generic plane core and its userspace ABI with vm-planes-merged:
the kvm_vcpu_common/kvm_plane layout and helpers, the KVM_CAP_PLANES /
KVM_EXIT_PLANE_EVENT definitions and documentation, and the x86 handling
that exits VM-plane and VBS hypercalls to userspace. These deltas came
from the integration branch's merge-commit conflict resolutions.
---
Documentation/virt/kvm/api.rst | 35 +++++++-------
arch/x86/kvm/svm/sev.c | 5 +-
arch/x86/kvm/x86.c | 86 ++++++++++------------------------
include/linux/kvm_host.h | 8 ++--
include/uapi/linux/kvm.h | 23 +--------
virt/kvm/kvm_main.c | 86 +++++++++++++++-------------------
6 files changed, 86 insertions(+), 157 deletions(-)
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index c6b109fa8945..269be00c8dcf 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -9101,27 +9101,20 @@ helpful if user space wants to emulate instructions which are not
This capability can be enabled dynamically even if VCPUs were already
created and are running.
-hpage_2g module parameter is not set to 1, -EINVAL is returned.
-
-7.47 KVM_CAP_PLANES_FPU
------------------------
-
-:Architectures: x86
-:Parameters: arg[0] is 0 if each vCPU plane has a separate FPU,
- 1 if the FPU is shared
-:Type: vm
+7.47 KVM_CAP_S390_HPAGE_2G
+--------------------------
-When enabled, such as KVM_SET_XSAVE or KVM_SET_FPU *are* available for
-vCPU on all planes, but they will read and write the same data that is presented
-to other planes. Note that KVM_GET/SET_XSAVE also allows access to some
-registers that are *not* part of FPU state; right now this is just PKRU.
-Those are never shared.
+:Architectures: s390
+:Parameters: none
+:Returns: 0 on success; -EINVAL if hpage_2g module parameter was not set,
+ cmma is enabled, or the VM has the KVM_VM_S390_UCONTROL
+ flag set; -EBUSY if vCPUs were already created for the VM.
-KVM_CAP_PLANES_FPU is experimental; userspace must *not* assume that
-KVM_CAP_PLANES_FPU is present on x86 for *any* VM type and different
-VM types may or may not allow enabling KVM_CAP_PLANES_FPU. Like for other
-capabilities, KVM_CAP_PLANES_FPU can be queried on the VM file descriptor;
-KVM_CHECK_EXTENSION returns 1 if it is possible to enable shared FPU mode.
+With this capability the KVM support for memory backing with 2g pages
+through hugetlbfs can be enabled for a VM. After the capability is
+enabled, cmma can't be enabled anymore and pfmfi and the storage key
+interpretation are disabled. If cmma has already been enabled or the
+hpage_2g module parameter is not set to 1, -EINVAL is returned.
8. Other capabilities.
======================
@@ -9674,6 +9667,10 @@ check for this capability on the VM file descriptor.
When called on the system file descriptor, KVM returns the highest
value supported on any machine type.
+When called on a plane file descriptor, KVM returns 0, because a
+plane cannot host planes of its own. Other capabilities are
+forwarded to the plane's parent VM.
+
8.47 KVM_CAP_S390_VSIE_ESAMODE
------------------------------
diff --git a/arch/x86/kvm/svm/sev.c b/arch/x86/kvm/svm/sev.c
index b9b0bbb72394..b94de3b8967a 100644
--- a/arch/x86/kvm/svm/sev.c
+++ b/arch/x86/kvm/svm/sev.c
@@ -4488,7 +4488,7 @@ static void sev_get_apic_ids(struct vcpu_svm *svm)
desc->num_entries = n;
kvm_for_each_vcpu(i, loop_vcpu, kvm) {
/*TODO: is this possible? */
- if (i > n)
+ if (i >= n)
break;
desc->apic_ids[i] = loop_vcpu->vcpu_id;
@@ -4713,6 +4713,9 @@ static bool is_snp_only_vmgexit(u64 exit_code)
case SVM_VMGEXIT_GUEST_REQUEST:
case SVM_VMGEXIT_EXT_GUEST_REQUEST:
case SVM_VMGEXIT_PSC:
+ case SVM_VMGEXIT_HVDB_PAGE:
+ case SVM_VMGEXIT_HV_IPI:
+ case SVM_VMGEXIT_SNP_RUN_VMPL:
return true;
default:
return false;
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c8c37d569023..d80b1caefc70 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -517,31 +517,6 @@ void kvm_free_plane(struct kvm_plane *plane)
kvm_x86_call(free_plane)(plane);
}
-struct kvm_plane *x86_alloc_plane(void)
-{
- /* For better type checking, do not return kzalloc() value directly */
- struct kvm_plane *plane = kzalloc(sizeof(*plane), GFP_KERNEL_ACCOUNT);
-
- return plane;
-}
-EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_alloc_plane);
-
-void x86_free_plane(struct kvm_plane *plane)
-{
- kfree(plane);
-}
-EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_free_plane);
-
-struct kvm_plane *kvm_alloc_plane(void)
-{
- return kvm_x86_call(alloc_plane)();
-}
-
-void kvm_free_plane(struct kvm_plane *plane)
-{
- kvm_x86_call(free_plane)(plane);
-}
-
/*
* All feature MSRs except uCode revID, which tracks the currently loaded uCode
* patch, are immutable once the vCPU model is defined.
@@ -1026,7 +1001,7 @@ static int complete_emulated_insn_gp(struct kvm_vcpu *vcpu, int err)
void kvm_inject_page_fault(struct kvm_vcpu *vcpu, struct x86_exception *fault,
bool from_hardware)
{
- ++vcpu->stat->pf_guest;
+ ++vcpu->stat.pf_guest;
/*
* Async #PF in L2 is always forwarded to L1 as a VM-Exit regardless of
@@ -3732,7 +3707,7 @@ static void kvmclock_reset(struct kvm_vcpu *vcpu)
static void kvm_vcpu_flush_tlb_all(struct kvm_vcpu *vcpu)
{
- ++vcpu->stat->tlb_flush;
+ ++vcpu->stat.tlb_flush;
kvm_x86_call(flush_tlb_all)(vcpu);
/* Flushing all ASIDs flushes the current ASID... */
@@ -3741,7 +3716,7 @@ static void kvm_vcpu_flush_tlb_all(struct kvm_vcpu *vcpu)
static void kvm_vcpu_flush_tlb_guest(struct kvm_vcpu *vcpu)
{
- ++vcpu->stat->tlb_flush;
+ ++vcpu->stat.tlb_flush;
if (!tdp_enabled) {
/*
@@ -3766,7 +3741,7 @@ static void kvm_vcpu_flush_tlb_guest(struct kvm_vcpu *vcpu)
static inline void kvm_vcpu_flush_tlb_current(struct kvm_vcpu *vcpu)
{
- ++vcpu->stat->tlb_flush;
+ ++vcpu->stat.tlb_flush;
kvm_x86_call(flush_tlb_current)(vcpu);
}
@@ -5305,11 +5280,11 @@ static void kvm_steal_time_set_preempted(struct kvm_vcpu *vcpu)
* preempted if and only if the VM-Exit was due to a host interrupt.
*/
if (!vcpu->arch.at_instruction_boundary) {
- vcpu->stat->preemption_other++;
+ vcpu->stat.preemption_other++;
return;
}
- vcpu->stat->preemption_reported++;
+ vcpu->stat.preemption_reported++;
if (!(vcpu->arch.st.msr_val & KVM_MSR_ENABLED))
return;
@@ -6845,7 +6820,7 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
r = -EEXIST;
if (irqchip_in_kernel(kvm) || kvm->has_planes)
goto split_irqchip_unlock;
- if (kvm->created_vcpus || kvm->has_planes)
+ if (kvm->created_vcpus)
goto split_irqchip_unlock;
/* Pairs with irqchip_in_kernel. */
smp_wmb();
@@ -9278,7 +9253,7 @@ static int handle_emulation_failure(struct kvm_vcpu *vcpu, int emulation_type)
{
struct kvm *kvm = vcpu->kvm;
- ++vcpu->stat->insn_emulation_fail;
+ ++vcpu->stat.insn_emulation_fail;
trace_kvm_emulate_insn_failed(vcpu);
if (emulation_type & EMULTYPE_VMWARE_GP) {
@@ -9510,7 +9485,7 @@ int x86_decode_emulated_instruction(struct kvm_vcpu *vcpu, int emulation_type,
r = x86_decode_insn(ctxt, insn, insn_len, emulation_type);
trace_kvm_emulate_insn_start(vcpu);
- ++vcpu->stat->insn_emulation;
+ ++vcpu->stat.insn_emulation;
return r;
}
@@ -9685,7 +9660,7 @@ int x86_emulate_instruction(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa,
}
r = 0;
} else if (vcpu->mmio_needed) {
- ++vcpu->stat->mmio_exits;
+ ++vcpu->stat.mmio_exits;
if (!vcpu->mmio_is_write)
writeback = false;
@@ -10452,7 +10427,7 @@ static void kvm_sched_yield(struct kvm_vcpu *vcpu, unsigned long dest_id)
struct kvm_vcpu *target = NULL;
struct kvm_apic_map *map;
- vcpu->stat->directed_yield_attempted++;
+ vcpu->stat.directed_yield_attempted++;
if (single_task_running())
goto no_yield;
@@ -10478,7 +10453,7 @@ static void kvm_sched_yield(struct kvm_vcpu *vcpu, unsigned long dest_id)
if (kvm_vcpu_yield_to(target) <= 0)
goto no_yield;
- vcpu->stat->directed_yield_successful++;
+ vcpu->stat.directed_yield_successful++;
no_yield:
return;
@@ -10555,7 +10530,7 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
int op_64_bit = is_64_bit_hypercall(vcpu);
unsigned long ret, nr, a0, a1, a2, a3;
- ++vcpu->stat->hypercalls;
+ ++vcpu->stat.hypercalls;
if (op_64_bit) {
nr = kvm_rax_read_raw(vcpu);
@@ -10673,7 +10648,7 @@ int ____kvm_emulate_hypercall(struct kvm_vcpu *vcpu, int cpl,
if (common->vtl_plane_ready) {
/* Parked in vtl_return: deliver now. */
- kvm_rax_write(secure, a0);
+ kvm_rax_write_raw(secure, a0);
common->vtl_call_pending = false;
} else {
/* Still booting: deliver on readiness. */
@@ -11388,7 +11363,7 @@ void kvm_inc_or_dec_irq_window_inhibit(struct kvm *kvm, bool inc)
*/
guard(rwsem_write)(&kvm->arch.apicv_update_lock);
if (atomic_add_return(add, &kvm->arch.apicv_nr_irq_window_req) == inc)
- __kvm_set_or_clear_apicv_inhibit(kvm->planes[0], APICV_INHIBIT_REASON_IRQWIN, inc);
+ __kvm_set_or_clear_apicv_inhibit(kvm, APICV_INHIBIT_REASON_IRQWIN, inc);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_inc_or_dec_irq_window_inhibit);
@@ -11644,22 +11619,9 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
goto out;
}
- if (kvm_check_plane0_events(vcpu)) {
- kvm_vcpu_set_plane_runnable(vcpu->common->vcpus[0]);
-
- kvm_make_request(KVM_REQ_EVENT, vcpu);
- kvm_make_request(KVM_REQ_PLANE_RESCHED, vcpu);
- }
-
- if (kvm_check_request(KVM_REQ_PLANE_RESCHED, vcpu)) {
- vcpu->common->plane_switch = true;
- r = 0;
- goto out;
- }
-
if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win ||
kvm_xen_has_interrupt(vcpu)) {
- ++vcpu->stat->req_event;
+ ++vcpu->stat.req_event;
r = kvm_apic_accept_events(vcpu);
if (r < 0) {
r = 0;
@@ -11815,7 +11777,7 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
run_flags = 0;
/* Note, VM-Exits that go down the "slow" path are accounted below. */
- ++vcpu->stat->exits;
+ ++vcpu->stat.exits;
}
kvm_load_host_pkru(vcpu);
@@ -11881,11 +11843,11 @@ static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
* VM-Exit on SVM and any ticks that occur between VM-Exit and now.
* An instruction is required after local_irq_enable() to fully unblock
* interrupts on processors that implement an interrupt shadow, the
- * stat->exits increment will do nicely.
+ * stat.exits increment will do nicely.
*/
kvm_before_interrupt(vcpu, KVM_HANDLING_IRQ);
local_irq_enable();
- ++vcpu->stat->exits;
+ ++vcpu->stat.exits;
local_irq_disable();
kvm_after_interrupt(vcpu);
@@ -12103,7 +12065,7 @@ static int vcpu_run(struct kvm_vcpu *vcpu)
kvm_vcpu_ready_for_interrupt_injection(vcpu)) {
r = 0;
vcpu->run->exit_reason = KVM_EXIT_IRQ_WINDOW_OPEN;
- ++vcpu->stat->request_irq_exits;
+ ++vcpu->stat.request_irq_exits;
break;
}
@@ -12128,7 +12090,7 @@ static int __kvm_emulate_halt(struct kvm_vcpu *vcpu, int state, int reason)
* managed by userspace, in which case userspace is responsible for
* handling wake events.
*/
- ++vcpu->stat->halt_exits;
+ ++vcpu->stat.halt_exits;
if (lapic_in_kernel(vcpu)) {
if (kvm_vcpu_has_events(vcpu) || vcpu->arch.pv.pv_unhalted)
state = KVM_MP_STATE_RUNNABLE;
@@ -12300,7 +12262,7 @@ static void kvm_put_guest_fpu(struct kvm_vcpu *vcpu)
return;
fpu_swap_kvm_fpstate(&vcpu->arch.guest_fpu, false);
- ++vcpu->stat->fpu_reload;
+ ++vcpu->stat.fpu_reload;
trace_kvm_fpu(0);
}
@@ -12387,7 +12349,7 @@ static int __kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
if (signal_pending(current)) {
r = -EINTR;
kvm_run->exit_reason = KVM_EXIT_INTR;
- ++vcpu->stat->signal_exits;
+ ++vcpu->stat.signal_exits;
}
goto out;
}
@@ -13180,7 +13142,7 @@ int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id)
return 0;
}
-int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu, struct kvm_plane *plane)
+int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
{
int r;
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 05c9edd4a73d..bee8eaea05bc 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -453,8 +453,7 @@ struct kvm_vcpu {
#endif
struct kvm_vcpu_arch arch;
- struct kvm_vcpu_stat *stat;
- struct kvm_vcpu_stat __stat;
+ struct kvm_vcpu_stat stat;
char stats_id[KVM_STATS_NAME_SIZE];
/*
@@ -1012,7 +1011,6 @@ struct kvm {
bool dirty_ring_with_bitmap;
bool vm_bugged;
bool vm_dead;
- bool has_planes;
#ifdef CONFIG_HAVE_KVM_PM_NOTIFIER
struct notifier_block pm_notifier;
@@ -1801,7 +1799,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_load(struct kvm_vcpu *vcpu, int cpu);
void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu);
int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id);
-int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu, struct kvm_plane *plane);
+int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_postcreate(struct kvm_vcpu *vcpu);
void kvm_arch_vcpu_destroy(struct kvm_vcpu *vcpu);
@@ -2664,7 +2662,7 @@ static inline int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
static inline void kvm_handle_signal_exit(struct kvm_vcpu *vcpu)
{
vcpu->run->exit_reason = KVM_EXIT_INTR;
- vcpu->stat->signal_exits++;
+ vcpu->stat.signal_exits++;
}
static inline int kvm_xfer_to_guest_mode_handle_work(struct kvm_vcpu *vcpu)
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 3118b31d13f6..fa2799c4dddb 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -140,16 +140,6 @@ struct kvm_xen_exit {
} u;
};
-struct kvm_plane_event_exit {
-#define KVM_PLANE_EVENT_INTERRUPT 1
- __u16 cause;
- __u16 pending_event_planes;
- __u16 target;
- __u16 padding;
- __u32 flags;
- __u64 extra[8];
-};
-
struct kvm_exit_snp_req_certs {
__u64 gpa;
__u64 npages;
@@ -243,13 +233,7 @@ struct kvm_run {
/* in */
__u8 request_interrupt_window;
__u8 HINT_UNSAFE_IN_KVM(immediate_exit);
-
- /* in/out */
- __u8 plane;
- __u16 suspended_planes;
-
- /* in */
- __u16 req_exit_planes;
+ __u8 padding1[6];
/* out */
__u32 exit_reason;
@@ -486,8 +470,6 @@ struct kvm_run {
__u64 gpa;
__u64 size;
} memory_fault;
- /* KVM_EXIT_PLANE_EVENT */
- struct kvm_plane_event_exit plane_event;
/* KVM_EXIT_TDX */
struct {
__u64 flags;
@@ -1709,7 +1691,4 @@ struct kvm_pre_fault_memory {
__u64 padding[5];
};
-#define KVM_CREATE_PLANE _IO(KVMIO, 0xd6)
-#define KVM_CREATE_VCPU_PLANE _IO(KVMIO, 0xd7)
-
#endif /* __LINUX_KVM_H */
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 6e4f3f3e6881..6b2d272797d3 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -440,7 +440,7 @@ void *kvm_mmu_memory_cache_alloc(struct kvm_mmu_memory_cache *mc)
static int kvm_vcpu_init_common(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned long id)
{
- struct kvm_vcpu_common *common = kzalloc(sizeof(*common), GFP_KERNEL_ACCOUNT);
+ struct kvm_vcpu_common *common __free(kfree) = kzalloc(sizeof(*common), GFP_KERNEL_ACCOUNT);
struct page *page;
int r;
@@ -503,10 +503,7 @@ static int kvm_vcpu_init_common(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned
if (r)
goto out_free_dirty_ring;
- vcpu->common = common;
-
- kvm_vcpu_set_in_spin_loop(vcpu, false);
- kvm_vcpu_set_dy_eligible(vcpu, false);
+ vcpu->common = no_free_ptr(common);
kvm_vcpu_set_in_spin_loop(vcpu, false);
kvm_vcpu_set_dy_eligible(vcpu, false);
@@ -522,8 +519,6 @@ static int kvm_vcpu_init_common(struct kvm_vcpu *vcpu, struct kvm *kvm, unsigned
kvm->created_vcpus--;
mutex_unlock(&kvm->lock);
- kfree(common);
-
return r;
}
@@ -1243,7 +1238,6 @@ static struct kvm_plane *kvm_create_plane(struct kvm *kvm, unsigned plane_level)
if (kvm_arch_plane_init(kvm, plane, plane_level))
goto out_free_plane;
-
kvm->planes[plane_level] = plane;
return plane;
@@ -1490,6 +1484,7 @@ static void kvm_destroy_vm(struct kvm *kvm)
#ifdef CONFIG_KVM_GENERIC_MEMORY_ATTRIBUTES
xa_destroy(&kvm->mem_attr_array);
#endif
+ kvm_destroy_planes(kvm);
kvm_arch_free_vm(kvm);
kvm_destroy_planes(kvm);
preempt_notifier_dec();
@@ -4383,6 +4378,7 @@ static int kvm_plane_ioctl_create_vcpu(struct kvm_plane *plane, unsigned long id
{
struct kvm *kvm = plane->kvm;
struct kvm_vcpu *vcpu;
+ struct kvm_vcpu *prev_current_vcpu;
int r;
mutex_lock(&kvm->lock);
@@ -4427,7 +4423,25 @@ static int kvm_plane_ioctl_create_vcpu(struct kvm_plane *plane, unsigned long id
kvm_vcpu_init(vcpu, kvm, id);
- r = kvm_arch_vcpu_create(vcpu, plane);
+ /*
+ * For planes above plane-0 the vCPU shares plane-0's kvm_vcpu_common,
+ * including ->current_vcpu and the preempt notifier consulted by
+ * kvm_sched_in()/kvm_sched_out(). kvm_arch_vcpu_create() (and
+ * kvm_arch_vcpu_postcreate() below) load this vCPU's VMCS via
+ * vcpu_load() but do not update ->current_vcpu, which still points at
+ * plane-0's vCPU. The arch create path performs GFP_KERNEL
+ * allocations, so the creating task can sleep and be rescheduled while
+ * this vCPU's VMCS is loaded; the shared notifier would then
+ * save/restore plane-0's vCPU and desync the per-CPU loaded_vmcs
+ * tracking from the hardware-current VMCS, wedging VMX (host hard
+ * lockup). Mirror the run loop's invariant (see
+ * kvm_vcpu_select_plane()): make ->current_vcpu the vCPU whose VMCS is
+ * loaded for the duration, then restore it.
+ */
+ prev_current_vcpu = vcpu->common->current_vcpu;
+ vcpu->common->current_vcpu = vcpu;
+ r = kvm_arch_vcpu_create(vcpu);
+ vcpu->common->current_vcpu = prev_current_vcpu;
if (r)
goto vcpu_free_common;
@@ -4456,14 +4470,18 @@ static int kvm_plane_ioctl_create_vcpu(struct kvm_plane *plane, unsigned long id
kvm_vcpu_unlock(vcpu);
mutex_unlock(&kvm->lock);
+ /* Same VMCS/current_vcpu invariant as above (vcpu_load in postcreate). */
+ prev_current_vcpu = vcpu->common->current_vcpu;
+ vcpu->common->current_vcpu = vcpu;
kvm_arch_vcpu_postcreate(vcpu);
+ vcpu->common->current_vcpu = prev_current_vcpu;
kvm_create_vcpu_debugfs(vcpu);
return r;
kvm_put_xa_erase:
kvm_vcpu_unlock(vcpu);
kvm_put_kvm_no_destroy(kvm);
- xa_erase(&kvm->planes[0]->vcpu_array, vcpu->vcpu_idx);
+ xa_erase(&plane->vcpu_array, vcpu->vcpu_idx);
unlock_vcpu_destroy:
mutex_unlock(&kvm->lock);
kvm_arch_vcpu_destroy(vcpu);
@@ -4614,38 +4632,16 @@ static int kvm_wait_for_vcpu_online(struct kvm_vcpu *vcpu)
static inline bool kvm_is_vcpu_plane_ioctl(unsigned ioctl)
{
switch (ioctl) {
- case KVM_GET_DEBUGREGS:
- case KVM_SET_DEBUGREGS:
case KVM_GET_FPU:
case KVM_SET_FPU:
- case KVM_GET_LAPIC:
- case KVM_SET_LAPIC:
- case KVM_GET_MSRS:
- case KVM_SET_MSRS:
- case KVM_GET_NESTED_STATE:
- case KVM_SET_NESTED_STATE:
- case KVM_GET_ONE_REG:
- case KVM_SET_ONE_REG:
case KVM_GET_REGS:
case KVM_SET_REGS:
case KVM_GET_SREGS:
case KVM_SET_SREGS:
- case KVM_GET_SREGS2:
- case KVM_SET_SREGS2:
- case KVM_GET_VCPU_EVENTS:
- case KVM_SET_VCPU_EVENTS:
- case KVM_GET_XCRS:
- case KVM_SET_XCRS:
- case KVM_GET_XSAVE:
- case KVM_GET_XSAVE2:
- case KVM_SET_XSAVE:
-
- case KVM_GET_REG_LIST:
case KVM_TRANSLATE:
return true;
-
default:
- return false;
+ return kvm_arch_is_vcpu_plane_ioctl(ioctl);
}
}
@@ -4950,7 +4946,6 @@ static int kvm_vm_ioctl_check_extension_generic(struct kvm *kvm, long arg);
static long __kvm_plane_ioctl(struct kvm_plane *plane, unsigned int ioctl, unsigned long arg)
{
- void __user *argp = (void __user *)arg;
long r;
switch (ioctl) {
@@ -4966,38 +4961,35 @@ static long __kvm_plane_ioctl(struct kvm_plane *plane, unsigned int ioctl, unsig
break;
#ifdef CONFIG_HAVE_KVM_MSI
case KVM_SIGNAL_MSI: {
+ void __user *argp = (void __user *)arg;
struct kvm_msi msi;
- r = -EFAULT;
if (copy_from_user(&msi, argp, sizeof(msi)))
- goto out;
+ return -EFAULT;
r = kvm_send_userspace_msi(plane->kvm, &msi, plane->level);
break;
}
#endif
#ifdef CONFIG_HAVE_KVM_IRQ_ROUTING
case KVM_SET_GSI_ROUTING: {
+ void __user *argp = (void __user *)arg;
struct kvm_irq_routing routing;
struct kvm_irq_routing __user *urouting;
struct kvm_irq_routing_entry *entries = NULL;
- r = -EFAULT;
if (copy_from_user(&routing, argp, sizeof(routing)))
- goto out;
- r = -EINVAL;
- if (!kvm_arch_can_set_irq_routing(plane->kvm))
- goto out;
- if (routing.nr > KVM_MAX_IRQ_ROUTES)
- goto out;
- if (routing.flags)
- goto out;
+ return -EFAULT;
+ if (!kvm_arch_can_set_irq_routing(plane->kvm) ||
+ routing.nr > KVM_MAX_IRQ_ROUTES ||
+ routing.flags)
+ return -EINVAL;
if (routing.nr) {
urouting = argp;
entries = vmemdup_array_user(urouting->entries,
routing.nr, sizeof(*entries));
if (IS_ERR(entries)) {
r = PTR_ERR(entries);
- goto out;
+ return r;
}
}
r = kvm_set_irq_routing(plane->kvm, entries, routing.nr,
@@ -5010,7 +5002,6 @@ static long __kvm_plane_ioctl(struct kvm_plane *plane, unsigned int ioctl, unsig
r = -ENOTTY;
}
-out:
return r;
}
@@ -5590,7 +5581,6 @@ static int kvm_vm_ioctl_create_plane(struct kvm *kvm, unsigned id)
goto put_kvm;
}
- kvm->planes[id] = plane;
kvm->has_planes = true;
fd_install(fd, file);
return fd;
--
2.55.0
^ permalink raw reply related [flat|nested] 43+ messages in thread
end of thread, other threads:[~2026-08-05 11:04 UTC | newest]
Thread overview: 43+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 11:02 [RFC PATCH v1 00/42] VBS/VSM-on-KVM: VBS integration for KVM VM planes Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 01/42] Fix merge issue - Remove duplicate definition for kvm_arch_has_irq_bypass Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 02/42] Fix compilation Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 03/42] Fix compile error Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 04/42] Fix compile errors Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 05/42] Initial support for VM Planes - Add kernel config for CONFIG_VM_PLANES - Parse vm plane config from initrd for plane configuration - Make hypercalls to allocate memory for the vm planes Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 06/42] Use vcpu count from the plane configuration Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 07/42] skip processing plane configuration for plane 0 - plane 0 is the boot plane Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 08/42] Add plane config param to specify kernel image format Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 09/42] Activate the VM Planes through the Hypervisor - Using KVM as the VMM Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 10/42] allow the command line to be specified for kernels in other planes Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 11/42] Various changes to support VM Planes Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 12/42] Add a Virtualization Based Security (VBS) framework. - Add backends for AMD SEV-SNP, Intel TDX, Arm CCA and KVM Planes. - Support VTL on Hyper-V in addition to Planes on KVM Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 13/42] Add a inter-plane communication mechanism through KVM. - model this to use a single page similar to SEV-SNP Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 14/42] KVM: Add per-plane memory attribute support for cross-plane EPT protection Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 15/42] KVM: x86: Add KVM_HC_VBS_VTL_CALL hypercall for VBS inter-plane calls Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 16/42] vbs: Add HEKI kernel sealing and fix KVM plane memory attribute guards Sriram Nambakam
2026-08-05 11:02 ` [RFC PATCH v1 17/42] vbs: Add module authentication via VBS/HEKI Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 18/42] vbs: Add kexec validation and make module auth non-fatal Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 19/42] Merge branch 'master' into vm-planes Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 20/42] kvm: x86: fix merged plane API/stat build regressions Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 21/42] KVM: x86: exit VM planes and VBS hypercalls to userspace Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 22/42] kexec: block legacy kexec_load when VBS is active Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 23/42] kvm: x86: fix merged plane API/stat build regressions Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 24/42] KVM: planes: expose memory-attribute setting to in-kernel callers Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 25/42] vm_planes: drop unused per-plane vcpu_count Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 26/42] drivers/virt: add VBS secure-plane park loop Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 27/42] KVM: planes: add arch-neutral in-kernel plane switch helper Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 28/42] KVM: x86: add VBS VTL call/return and cross-plane set-mem-attrs hypercalls Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 29/42] init/vm_planes: set up planes from rootfs_initcall and load ELF payloads Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 30/42] security/vbs: run backend probe and HEKI seal at rootfs_initcall Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 31/42] security/vbs: pin the VTL call hypercall to CPU0 Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 32/42] security/vbs: add secure-plane monitor backend Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 33/42] drivers/virt: rename VBS park loop to secure_monitor Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 34/42] x86/realmode: skip the sub-1M trampoline for the VBS secure plane Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 35/42] KVM: x86: deny normal-plane access to secure-plane memory Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 36/42] KVM: plane: handle KVM_CHECK_EXTENSION on the plane fd Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 37/42] KVM: selftests: run plane tests with a split IRQ chip Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 38/42] kvm: x86: drop obsolete kvm_cache_regs.h Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 39/42] kvm: arch: finalize plane hooks and kvm_arch_vcpu_create signature Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 40/42] kvm: x86: use kvm_vcpu scheduling-state accessors and struct stat fields Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 41/42] kvm: x86: finalize per-plane APIC state and CPUID placement Sriram Nambakam
2026-08-05 11:03 ` [RFC PATCH v1 42/42] kvm: planes: reconcile core plane state, UAPI and hypercall exit Sriram Nambakam
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox