* [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