* [RFC PATCH v2 1/8] KVM: x86: raise the default maximum planes to two
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 2/8] security/vbs: introduce core VBS framework Sriram Nambakam
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
The default max-planes callback returns 1, which limits KVM_CAP_PLANES to a
single plane on VMX (and on SVM for non-SEV-SNP guests). VBS needs a normal
plane (0) and one secure plane (1), so return 2 by default. This is still
gated by irqchip=split in kvm_arch_max_planes().
---
arch/x86/kvm/x86.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 35fbe0776a3e..29766c19c289 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -478,7 +478,8 @@ static unsigned int num_msr_based_features;
unsigned kvm_x86_default_max_planes(struct kvm *kvm)
{
- return 1;
+ /* Support a normal plane (0) and one secure plane (1) for VBS. */
+ return 2;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_x86_default_max_planes);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 2/8] security/vbs: introduce core VBS framework
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 1/8] KVM: x86: raise the default maximum planes to two Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 3/8] security/vbs: add platform probe and backend registration Sriram Nambakam
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add the transport-agnostic Virtualization-Based Security (VBS) core: a
small dispatch layer between the guest OS (plane-0) and a secure kernel
running in a higher-privileged plane-1.
Backends register a struct vbs_ops via vbs_register_backend(); the core
exposes vbs_available() and a generic vbs_vtl_call() that forwards to the
active backend. No backend is registered yet.
Gated by CONFIG_VBS (off by default).
---
include/linux/vbs.h | 74 +++++++++++++++++++++++++++++++++++++++++++
security/Kconfig | 2 ++
security/Makefile | 1 +
security/vbs/Kconfig | 16 ++++++++++
security/vbs/Makefile | 3 ++
security/vbs/core.c | 56 ++++++++++++++++++++++++++++++++
6 files changed, 152 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/core.c
diff --git a/include/linux/vbs.h b/include/linux/vbs.h
new file mode 100644
index 000000000000..a154396bf070
--- /dev/null
+++ b/include/linux/vbs.h
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * VBS — Virtualization-Based Security
+ *
+ * Transport-agnostic interface between the guest OS (plane-0) and a secure
+ * kernel running in a higher-privileged plane-1. The guest kernel calls the
+ * vbs_*() functions; the active backend translates them into the appropriate
+ * transport (e.g. a KVM paravirt hypercall).
+ *
+ * This is the core framework only. VBS is software-only: backends are
+ * software/hypervisor planes (KVM software planes now, Hyper-V VSM later).
+ * Backends register via vbs_register_backend().
+ */
+
+#ifndef _LINUX_VBS_H
+#define _LINUX_VBS_H
+
+#include <linux/types.h>
+#include <linux/errno.h>
+
+/* VTL-call request codes (plane-0 -> plane-1 direction). */
+enum vbs_call_id {
+ VBS_CALL_INIT = 0x0001, /* plane-0 boot complete: load plane */
+ VBS_CALL_SHUTDOWN = 0x0002, /* plane-0 shutting down: unload */
+};
+
+/**
+ * struct vbs_ops - operations provided by a VBS backend
+ * @name: backend name, e.g. "kvm-planes"
+ * @init: load/connect the secure plane; called once after drivers init
+ * @shutdown: unload the secure plane; called on reboot/halt
+ * @vtl_call: send an arbitrary request to the secure kernel and wait for a
+ * response. Returns 0 on success, negative errno on failure.
+ *
+ * Callbacks run from process context with preemption enabled.
+ */
+struct vbs_ops {
+ const char *name;
+
+ int (*init)(void);
+ void (*shutdown)(void);
+
+ int (*vtl_call)(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size);
+};
+
+#ifdef CONFIG_VBS
+
+/**
+ * vbs_register_backend() - register the platform-specific backend.
+ *
+ * Called once during 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() - true if a backend is registered. */
+bool vbs_available(void);
+
+/** vbs_vtl_call() - dispatch a raw VTL call through the active backend. */
+int vbs_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size);
+
+#else /* !CONFIG_VBS */
+
+static inline bool vbs_available(void) { return false; }
+static inline int vbs_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size) { 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..80214c702ddc 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS) += device_cgroup.o
obj-$(CONFIG_BPF_LSM) += bpf/
obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
obj-$(CONFIG_SECURITY_IPE) += ipe/
+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..0e482196c5b7
--- /dev/null
+++ b/security/vbs/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VBS
+ bool "Virtualization-Based Security (VBS) support"
+ depends on X86_64
+ help
+ Enable a transport-agnostic interface between the guest OS
+ (plane-0) and a secure kernel running in a higher-privileged
+ plane-1.
+
+ The core VBS layer dispatches calls from kernel subsystems to a
+ platform-specific backend. VBS is software-only: backends are
+ software/hypervisor planes (KVM software planes now, Hyper-V VSM
+ later). Hardware confidential-compute is out of scope.
+
+ If unsure, say N.
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
new file mode 100644
index 000000000000..952c2b855465
--- /dev/null
+++ b/security/vbs/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_VBS) += vbs.o
+vbs-y := core.o
diff --git a/security/vbs/core.c b/security/vbs/core.c
new file mode 100644
index 000000000000..407d49a91b8f
--- /dev/null
+++ b/security/vbs/core.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS — Virtualization-Based Security core
+ *
+ * Dispatches calls from guest kernel subsystems to the active
+ * platform-specific backend.
+ */
+
+#include <linux/vbs.h>
+#include <linux/export.h>
+#include <linux/mutex.h>
+#include <linux/printk.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);
+
+int vbs_vtl_call(enum vbs_call_id id,
+ const void *arg, size_t arg_size,
+ void *resp, size_t resp_size)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (!ops)
+ return -ENODEV;
+ if (!ops->vtl_call)
+ return -EOPNOTSUPP;
+ return ops->vtl_call(id, arg, arg_size, resp, resp_size);
+}
+EXPORT_SYMBOL_GPL(vbs_vtl_call);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 3/8] security/vbs: add platform probe and backend registration
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 1/8] KVM: x86: raise the default maximum planes to two Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 2/8] security/vbs: introduce core VBS framework Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 4/8] security/vbs: add KVM software planes backend Sriram Nambakam
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add a single rootfs_initcall that walks a probe table and registers the
first backend whose detect() succeeds. The table currently holds only the
KVM software-planes entry; when that backend is not configured a local stub
keeps the probe self-contained and buildable.
Registration only records the backend at this stage.
---
security/vbs/Makefile | 4 ++-
security/vbs/internal.h | 20 ++++++++++++++
security/vbs/probe.c | 61 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 security/vbs/internal.h
create mode 100644 security/vbs/probe.c
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
index 952c2b855465..0fcbb6640ec1 100644
--- a/security/vbs/Makefile
+++ b/security/vbs/Makefile
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_VBS) += vbs.o
-vbs-y := core.o
+# probe.o links before core.o so the backend is registered (vbs_probe_init)
+# early in the rootfs_initcall level.
+vbs-y := probe.o core.o
diff --git a/security/vbs/internal.h b/security/vbs/internal.h
new file mode 100644
index 000000000000..2f444781b390
--- /dev/null
+++ b/security/vbs/internal.h
@@ -0,0 +1,20 @@
+/* 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/printk.h>
+#include <linux/vbs.h>
+
+/* Each backend exports a detect + get_ops pair for the centralized probe. */
+
+#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/probe.c b/security/vbs/probe.c
new file mode 100644
index 000000000000..ccaaba93b18b
--- /dev/null
+++ b/security/vbs/probe.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS platform detection and backend selection
+ *
+ * A single boot-time initcall probes the platform and registers the
+ * appropriate VBS backend. Only one backend can be active; the first
+ * successful probe wins. VBS is software-only: the only backend today is
+ * KVM software planes; other software backends (e.g. Hyper-V VSM) may be
+ * added later.
+ */
+
+#include "internal.h"
+
+/* Stub for the backend when it is not configured in. */
+#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
+
+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 = {
+ { "KVM planes", vbs_kvm_planes_detect, vbs_kvm_planes_get_ops },
+};
+
+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 rootfs_initcall level: platform detection is complete and the VM
+ * planes have been set up (init/ links before security/), but subsystems
+ * that consume VBS have not yet started. Registration only records the
+ * backend; the plane is loaded later, after device drivers initialise.
+ */
+rootfs_initcall(vbs_probe_init);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 4/8] security/vbs: add KVM software planes backend
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
` (2 preceding siblings ...)
2026-08-11 1:52 ` [RFC PATCH v2 3/8] security/vbs: add platform probe and backend registration Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 5/8] security/vbs: enable the backend after driver init Sriram Nambakam
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add the KVM software-planes VBS backend. It uses a synchronous
shared-memory calling area and the KVM_HC_VBS_VTL_CALL paravirt hypercall
(handled by the host) to reach plane-1.
Only the plane lifecycle is implemented: init() allocates the calling
area and issues VBS_CALL_INIT to load (connect to) the secure plane;
shutdown() issues VBS_CALL_SHUTDOWN to unload it. The BSP-pinned
work_on_cpu() ensures the plane switch always lands on CPU0.
Gated by CONFIG_VBS_KVM_PLANES.
---
security/vbs/Kconfig | 15 ++++
security/vbs/Makefile | 2 +
security/vbs/kvm_planes.c | 177 ++++++++++++++++++++++++++++++++++++++
3 files changed, 194 insertions(+)
create mode 100644 security/vbs/kvm_planes.c
diff --git a/security/vbs/Kconfig b/security/vbs/Kconfig
index 0e482196c5b7..e21f4f30b6cf 100644
--- a/security/vbs/Kconfig
+++ b/security/vbs/Kconfig
@@ -14,3 +14,18 @@ config VBS
later). Hardware confidential-compute is out of scope.
If unsure, say N.
+
+config VBS_KVM_PLANES
+ bool "VBS backend: KVM software planes"
+ depends on VBS && KVM_GUEST
+ help
+ VBS backend that uses a KVM paravirt hypercall to communicate
+ between plane-0 (the normal guest kernel) and plane-1 (a secure
+ kernel running in a separate KVM VM plane managed by QEMU).
+
+ This minimal backend supports loading (connecting to) and
+ unloading the secure plane via a shared-memory calling area.
+
+ Select this if you are running under KVM with VM planes support.
+
+ If unsure, say N.
diff --git a/security/vbs/Makefile b/security/vbs/Makefile
index 0fcbb6640ec1..3a161e7cc279 100644
--- a/security/vbs/Makefile
+++ b/security/vbs/Makefile
@@ -3,3 +3,5 @@ obj-$(CONFIG_VBS) += vbs.o
# probe.o links before core.o so the backend is registered (vbs_probe_init)
# early in the rootfs_initcall level.
vbs-y := probe.o core.o
+
+obj-$(CONFIG_VBS_KVM_PLANES) += kvm_planes.o
diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c
new file mode 100644
index 000000000000..af9118c6e74f
--- /dev/null
+++ b/security/vbs/kvm_planes.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * VBS backend — KVM software planes
+ *
+ * Uses a KVM paravirt hypercall to communicate between plane-0 (the normal
+ * guest kernel) and plane-1 (a secure kernel running in a separate KVM plane
+ * managed by QEMU).
+ *
+ * Transport: kvm_hypercall1(KVM_HC_VBS_VTL_CALL, gpa) -> KVM_EXIT_HYPERCALL.
+ *
+ * The shared-memory VTL-call protocol is synchronous:
+ * 1. Plane-0 fills the request buffer in the shared calling area.
+ * 2. Plane-0 issues the hypercall carrying the physical address of the area.
+ * 3. Plane-1 processes the request and writes a response.
+ * 4. Plane-0 reads the response from the same page.
+ *
+ * This minimal backend implements only the plane lifecycle: init() loads
+ * (connects to) the secure plane, shutdown() unloads it.
+ */
+
+#include "internal.h"
+
+#include <linux/mm.h>
+#include <linux/gfp.h>
+#include <linux/io.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+#include <linux/kvm_para.h>
+#include <asm/kvm_para.h>
+
+/* ── shared-memory calling area ────────────────────────────────────────── */
+
+/*
+ * Single shared page used for both request and response data. The protocol
+ * is synchronous, so 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;
+
+#define VBS_CA_BUF_SIZE (PAGE_SIZE - sizeof(struct vbs_kvm_ca))
+
+static void *kvm_ca_page; /* single calling-area page */
+
+/* ── low-level VTL call ────────────────────────────────────────────────── */
+
+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 and the secure plane boots only on CPU0's sibling.
+ * Driven via work_on_cpu() so the hypercall always lands on CPU0.
+ */
+static long kvm_planes_vtl_call_on_cpu(void *data)
+{
+ struct kvm_vtl_call_ctx *ctx = data;
+ struct vbs_kvm_ca *ca = kvm_ca_page;
+ long hc_ret;
+
+ ca->call_id = ctx->id;
+ ca->arg_size = ctx->arg_size;
+ ca->status = 0;
+ ca->resp_size = 0;
+ if (ctx->arg_size && ctx->arg)
+ memcpy(ca->buffer, ctx->arg, ctx->arg_size);
+ ca->call_pending = 1;
+
+ hc_ret = kvm_hypercall1(KVM_HC_VBS_VTL_CALL, virt_to_phys(kvm_ca_page));
+ ca->call_pending = 0;
+
+ if (hc_ret) {
+ pr_err_ratelimited("vbs-kvm: hypercall failed (%ld)\n", hc_ret);
+ return -EIO;
+ }
+
+ if (ca->status)
+ return ca->status;
+
+ if (ctx->resp && ctx->resp_size && ca->resp_size) {
+ size_t copy = min_t(size_t, ctx->resp_size, ca->resp_size);
+
+ 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. */
+ return work_on_cpu(0, kvm_planes_vtl_call_on_cpu, &ctx);
+}
+
+/* ── lifecycle: load / unload the secure plane ─────────────────────────── */
+
+static int kvm_planes_init(void)
+{
+ int ret;
+
+ 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);
+ 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;
+}
+
+static void kvm_planes_shutdown(void)
+{
+ if (!kvm_ca_page)
+ return;
+
+ kvm_planes_vtl_call(VBS_CALL_SHUTDOWN, NULL, 0, NULL, 0);
+ free_page((unsigned long)kvm_ca_page);
+ kvm_ca_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,
+};
+
+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;
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 5/8] security/vbs: enable the backend after driver init
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
` (3 preceding siblings ...)
2026-08-11 1:52 ` [RFC PATCH v2 4/8] security/vbs: add KVM software planes backend Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 6/8] vm_planes: add hypervisor-assisted plane bootstrap Sriram Nambakam
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Activate a registered VBS backend from a late_initcall, once plane-0 is
otherwise up. Enabling is opt-in and requires two conditions:
1. the operator passes enable-kvm-planes=1 on the kernel command line, and
2. the boot image advertises a provisioned secure plane via
/etc/Kconfig.kvm-planes containing CONFIG_VM_PLANES=y.
When both hold, call the backend's init() to load the secure plane and
register a reboot notifier that invokes shutdown() to unload it.
---
security/vbs/core.c | 114 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 114 insertions(+)
diff --git a/security/vbs/core.c b/security/vbs/core.c
index 407d49a91b8f..c006b6d53a14 100644
--- a/security/vbs/core.c
+++ b/security/vbs/core.c
@@ -8,8 +8,15 @@
#include <linux/vbs.h>
#include <linux/export.h>
+#include <linux/init.h>
+#include <linux/kernel_read_file.h>
+#include <linux/kstrtox.h>
#include <linux/mutex.h>
#include <linux/printk.h>
+#include <linux/reboot.h>
+#include <linux/sizes.h>
+#include <linux/string.h>
+#include <linux/vmalloc.h>
static const struct vbs_ops *vbs_backend;
static DEFINE_MUTEX(vbs_lock);
@@ -54,3 +61,110 @@ int vbs_vtl_call(enum vbs_call_id id,
return ops->vtl_call(id, arg, arg_size, resp, resp_size);
}
EXPORT_SYMBOL_GPL(vbs_vtl_call);
+
+/* ── enable after driver init ────────────────────────────────── */
+
+/*
+ * A registered backend is only activated when two conditions hold:
+ * 1. the operator opts in on the kernel command line (enable-kvm-planes=1),
+ * and
+ * 2. the boot image advertises a provisioned secure plane via a plane
+ * configuration file that enables the expected option.
+ */
+#define VBS_KCONFIG_PATH "/etc/Kconfig.kvm-planes"
+#define VBS_KCONFIG_TOKEN "CONFIG_VM_PLANES=y"
+
+static bool vbs_enable_requested;
+
+static int __init vbs_parse_enable_kvm_planes(char *str)
+{
+ bool val;
+
+ /* Bare "enable-kvm-planes" (no value) means enabled. */
+ if (!str || !*str)
+ vbs_enable_requested = true;
+ else if (!kstrtobool(str, &val))
+ vbs_enable_requested = val;
+ return 0;
+}
+early_param("enable-kvm-planes", vbs_parse_enable_kvm_planes);
+
+static int vbs_reboot_notify(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+
+ if (ops && ops->shutdown)
+ ops->shutdown();
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block vbs_reboot_nb = {
+ .notifier_call = vbs_reboot_notify,
+};
+
+/* Return true if VBS_KCONFIG_PATH exists and enables the plane config. */
+static bool __init vbs_plane_config_present(void)
+{
+ void *buf = NULL;
+ size_t sz = 0;
+ bool ok = false;
+ int ret;
+
+ ret = kernel_read_file_from_path(VBS_KCONFIG_PATH, 0, &buf, SZ_1M, &sz,
+ READING_UNKNOWN);
+ if (ret < 0) {
+ pr_info("vbs: %s unavailable (%d); backend left idle\n",
+ VBS_KCONFIG_PATH, ret);
+ return false;
+ }
+
+ if (buf && sz)
+ ok = strnstr(buf, VBS_KCONFIG_TOKEN, sz) != NULL;
+ vfree(buf);
+
+ if (!ok)
+ pr_info("vbs: %s present but %s not set; backend left idle\n",
+ VBS_KCONFIG_PATH, VBS_KCONFIG_TOKEN);
+ return ok;
+}
+
+/*
+ * Enable the registered backend after device drivers have initialised.
+ * Runs at late_initcall so the plane is loaded only once the plane-0 kernel
+ * is otherwise up, the operator requested it (enable-kvm-planes=1), and the
+ * boot image advertises a plane config.
+ */
+static int __init vbs_enable(void)
+{
+ const struct vbs_ops *ops = READ_ONCE(vbs_backend);
+ int ret;
+
+ if (!ops) {
+ pr_debug("vbs: no backend registered; nothing to enable\n");
+ return 0;
+ }
+
+ if (!vbs_enable_requested) {
+ pr_info("vbs: enable-kvm-planes not set; backend \"%s\" left idle\n",
+ ops->name);
+ return 0;
+ }
+
+ if (!vbs_plane_config_present())
+ return 0;
+
+ if (ops->init) {
+ ret = ops->init();
+ if (ret) {
+ pr_warn("vbs: backend \"%s\" init failed (%d)\n",
+ ops->name, ret);
+ return 0;
+ }
+ }
+
+ register_reboot_notifier(&vbs_reboot_nb);
+ pr_info("vbs: enabled backend \"%s\"\n", ops->name);
+ return 0;
+}
+late_initcall(vbs_enable);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 6/8] vm_planes: add hypervisor-assisted plane bootstrap
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
` (4 preceding siblings ...)
2026-08-11 1:52 ` [RFC PATCH v2 5/8] security/vbs: enable the backend after driver init Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 7/8] security/vbs: bootstrap the plane from the enable path Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 8/8] drivers/virt: add KVM VM-planes secure-plane monitor Sriram Nambakam
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add the guest-side VM-planes bootstrap under CONFIG_VM_PLANES. It reads
the per-plane configuration (config-vm-planes) and the plane kernel images
from the initramfs, allocates plane memory and creates the planes via the
KVM_HC_VM_PLANES_CONFIG / KVM_HC_VM_PLANES_ACTIVATE paravirt hypercalls,
and loads RAW or ELF plane kernels into plane memory.
The orchestration is exposed as vm_planes_bootstrap() so a consumer can
drive it at the right point in boot; the x86 hypercall wrappers
alloc_vm_planes() / activate_vm_planes() live in cpu/common.c.
Only software planes are supported; there is no memory protection or
sealing here.
---
arch/x86/kernel/cpu/common.c | 64 ++++
include/linux/vm_planes.h | 46 +++
init/Kconfig | 18 +
init/Makefile | 4 +
init/vm_planes.c | 636 +++++++++++++++++++++++++++++++++++
5 files changed, 768 insertions(+)
create mode 100644 include/linux/vm_planes.h
create mode 100644 init/vm_planes.c
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index a3df21d26460..2489af105c18 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -28,8 +28,11 @@
#include <linux/stackprotector.h>
#include <linux/utsname.h>
#include <linux/efi.h>
+#include <linux/kvm_para.h>
+#include <linux/vm_planes.h>
#include <asm/alternative.h>
+#include <asm/kvm_para.h>
#include <asm/cmdline.h>
#include <asm/cpuid/api.h>
#include <asm/perf_event.h>
@@ -2664,3 +2667,64 @@ void __init arch_cpu_finalize_init(void)
*/
mem_encrypt_init();
}
+
+#ifdef CONFIG_VM_PLANES
+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 -EINVAL;
+
+ if (!kvm_para_available()) {
+ pr_warn("vm_planes: hypercall interface unavailable\n");
+ 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 -EOVERFLOW;
+ }
+
+ 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);
+ return (int)ret;
+ }
+
+ return 0;
+}
+
+int __init activate_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg)
+{
+ phys_addr_t phys;
+ long ret;
+
+ if (!plane_count || !plane_cfg)
+ return -EINVAL;
+
+ if (!kvm_para_available()) {
+ pr_warn("vm_planes: hypercall interface unavailable\n");
+ return -ENODEV;
+ }
+
+ 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;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_VM_PLANES */
diff --git a/include/linux/vm_planes.h b/include/linux/vm_planes.h
new file mode 100644
index 000000000000..e76cbfd99c6d
--- /dev/null
+++ b/include/linux/vm_planes.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_VM_PLANES_H
+#define _LINUX_VM_PLANES_H
+
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#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,
+ VM_PLANE_KFMT_BZIMAGE,
+ VM_PLANE_KFMT_ELF,
+};
+
+struct vm_plane_config {
+ phys_addr_t load_offset;
+ phys_addr_t memory_size;
+ phys_addr_t entry_point;
+ unsigned int kernel_format;
+ char kernel[VM_PLANE_KERNEL_NAME_MAX];
+ char cmdline[VM_PLANE_CMDLINE_MAX];
+};
+
+int __init load_vm_plane_kernels(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
+
+int __init alloc_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
+
+int __init activate_vm_planes(unsigned int plane_count,
+ struct vm_plane_config *plane_cfg);
+
+int __init vm_planes_bootstrap(void);
+
+#else /* !CONFIG_VM_PLANES */
+
+static inline int vm_planes_bootstrap(void) { return -ENODEV; }
+
+#endif /* CONFIG_VM_PLANES */
+
+#endif /* _LINUX_VM_PLANES_H */
diff --git a/init/Kconfig b/init/Kconfig
index 10f2013b5321..bdb692f38a90 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -2259,6 +2259,24 @@ source "kernel/Kconfig.kexec"
source "kernel/liveupdate/Kconfig"
+config VM_PLANES
+ bool "Enable VM planes early boot support" if EXPERT
+ depends on KVM_GUEST
+ default n
+ help
+ Enable hypervisor-assisted multi-kernel (VM planes) support.
+
+ When enabled, and when the VBS backend requests it, the kernel sets
+ up the configured planes from the VBS enable path (late_initcall,
+ after device drivers and before userspace). The plane configuration
+ and per-plane kernel images are read from the initramfs.
+
+ 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.
+
endmenu # General setup
source "arch/Kconfig"
diff --git a/init/Makefile b/init/Makefile
index d6f75d8907e0..100b3fcda37a 100644
--- a/init/Makefile
+++ b/init/Makefile
@@ -14,6 +14,10 @@ endif
obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
obj-$(CONFIG_INITRAMFS_TEST) += initramfs_test.o
+# vm_planes.o must link AFTER initramfs.o so that it reads the plane config
+# and kernels from the populated rootfs.
+obj-$(CONFIG_VM_PLANES) += vm_planes.o
+
obj-y += init_task.o
mounts-y := do_mounts.o
diff --git a/init/vm_planes.c b/init/vm_planes.c
new file mode 100644
index 000000000000..27994e4a915a
--- /dev/null
+++ b/init/vm_planes.c
@@ -0,0 +1,636 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/init.h>
+#include <linux/initrd.h>
+#include <linux/kernel.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 <linux/io.h>
+#include <asm/cpu.h>
+#include <asm/kvm_para.h>
+
+#ifdef CONFIG_VM_PLANES
+
+#define VM_PLANES_CONFIG_FILE "config-vm-planes"
+#define VM_PLANES_DEFAULT_COUNT 1
+
+struct vm_plane_parse_state {
+ phys_addr_t load_offset;
+ phys_addr_t memory_size;
+ 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)
+
+/*
+ * 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)
+{
+ 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;
+ }
+
+ *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)
+{
+ 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[VM_PLANE_CMDLINE_MAX + 64];
+ 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 -ENOENT;
+ *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].kernel, val,
+ sizeof(state[plane_id].kernel));
+ 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 (!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;
+
+ 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].load_offset = parsed;
+ return 0;
+ }
+
+ if (!strcmp(key, "MEMORY_SIZE")) {
+ plane_cfg[plane_id].memory_size = parsed;
+ state[plane_id].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 = kzalloc(*plane_count * sizeof(**plane_cfg), GFP_KERNEL);
+ if (!*plane_cfg)
+ return -ENOMEM;
+
+ state = kzalloc(*plane_count * sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ 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].kernel[0] = '\0';
+ state[i].cmdline[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 = 1; i < *plane_count; i++) {
+ if (state[i].load_offset == VM_PLANES_UNSET_VALUE ||
+ state[i].memory_size == VM_PLANES_UNSET_VALUE ||
+ !state[i].kernel[0])
+ return -EINVAL;
+ }
+
+ kfree(state);
+ return 0;
+}
+
+/* ---- Config loading via VFS ---- */
+
+static int __init vm_planes_get_cfg(unsigned int *plane_count,
+ struct vm_plane_config **plane_cfg)
+{
+ void *buf;
+ loff_t size;
+ int ret;
+
+ 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;
+ }
+
+ ret = parse_vm_planes_kconfig(buf, (size_t)size, plane_count, plane_cfg);
+ kvfree(buf);
+ return ret;
+}
+
+/* ---- Kernel loading ---- */
+
+static int __init copy_to_early_mem(phys_addr_t dest, const void *src,
+ unsigned long size)
+{
+ 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)
+{
+ void *p;
+
+ if (!size)
+ return 0;
+ p = memremap(dest, size, MEMREMAP_WB);
+ if (!p)
+ return -ENOMEM;
+ memset(p, 0, size);
+ memunmap(p);
+ return 0;
+}
+
+static int __init load_plane_kernel_elf(const u8 *data, u32 size,
+ struct vm_plane_config *cfg)
+{
+ const Elf64_Ehdr *ehdr;
+ const Elf64_Phdr *phdr;
+ unsigned int i;
+ int ret;
+
+ if (size < sizeof(*ehdr)) {
+ pr_err("vm_planes: ELF image too small (%u bytes)\n", size);
+ return -EINVAL;
+ }
+
+ ehdr = (const Elf64_Ehdr *)data;
+
+ if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) {
+ pr_err("vm_planes: not a valid ELF image\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ if (!ehdr->e_phnum || ehdr->e_phentsize != sizeof(Elf64_Phdr)) {
+ pr_err("vm_planes: invalid ELF program headers\n");
+ 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;
+ }
+
+ phdr = (const Elf64_Phdr *)(data + ehdr->e_phoff);
+
+ for (i = 0; i < ehdr->e_phnum; i++, phdr++) {
+ phys_addr_t dest;
+ u64 bss_size;
+
+ if (phdr->p_type != PT_LOAD)
+ continue;
+
+ if (!phdr->p_memsz)
+ continue;
+
+ /*
+ * 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;
+ }
+
+ if (phdr->p_offset + phdr->p_filesz > size) {
+ pr_err("vm_planes: ELF PT_LOAD file data beyond image\n");
+ return -EINVAL;
+ }
+
+ if (phdr->p_filesz) {
+ ret = copy_to_early_mem(dest, data + phdr->p_offset,
+ phdr->p_filesz);
+ if (ret)
+ return ret;
+ }
+
+ 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;
+ }
+
+ /*
+ * 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);
+ }
+
+ 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;
+}
+
+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;
+ }
+
+ cfg->entry_point = cfg->load_offset;
+ 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++) {
+ void *data;
+ loff_t fsize;
+ int ret;
+
+ ret = vm_planes_read_file(plane_cfg[i].kernel, &data, &fsize);
+ if (ret) {
+ 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, (u32)fsize,
+ &plane_cfg[i]);
+ break;
+ case VM_PLANE_KFMT_ELF:
+ 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: %d\n",
+ i, ret);
+ err = ret;
+ } else {
+ 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;
+}
+
+/* ---- Activation ---- */
+
+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,
+ struct vm_plane_config *plane_cfg) { return -ENOSYS; }
+
+/*
+ * Set up VM planes during boot.
+ *
+ * Invoked from the VBS enable path at late_initcall: after device drivers
+ * have initialised (so the rootfs is populated and the plane config and
+ * kernels can be read) and before userspace starts, so the secure plane
+ * vcpu exists by the time the first VTL call is issued.
+ */
+int __init vm_planes_bootstrap(void)
+{
+ unsigned int plane_count = VM_PLANES_DEFAULT_COUNT;
+ struct vm_plane_config *plane_cfg;
+ int ret;
+
+ /* 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 -ENODEV;
+ }
+
+ 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 ret;
+ }
+
+ pr_info("vm_planes: enabling %u planes (ids 0..%u)\n",
+ plane_count, plane_count - 1);
+
+ ret = alloc_vm_planes(plane_count, plane_cfg);
+ if (ret) {
+ pr_err("vm_planes: failed to allocate planes: %d\n", ret);
+ return ret;
+ }
+
+ 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;
+ }
+
+ ret = activate_vm_planes(plane_count, plane_cfg);
+ if (ret)
+ pr_err("vm_planes: failed to activate planes: %d\n", ret);
+
+ return ret;
+}
+
+#endif /* CONFIG_VM_PLANES */
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 7/8] security/vbs: bootstrap the plane from the enable path
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
` (5 preceding siblings ...)
2026-08-11 1:52 ` [RFC PATCH v2 6/8] vm_planes: add hypervisor-assisted plane bootstrap Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 8/8] drivers/virt: add KVM VM-planes secure-plane monitor Sriram Nambakam
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
When the KVM software-planes backend is enabled, create and activate the
secure plane before the backend issues its first VTL call. vbs_enable()
runs at late_initcall -- after device drivers have initialised and before
userspace starts -- which is where vm_planes_bootstrap() now runs.
Select VM_PLANES from VBS_KVM_PLANES so the plane bootstrap is built in
whenever the backend is.
---
security/vbs/Kconfig | 1 +
security/vbs/core.c | 12 ++++++++++++
2 files changed, 13 insertions(+)
diff --git a/security/vbs/Kconfig b/security/vbs/Kconfig
index e21f4f30b6cf..7a2ebc13e479 100644
--- a/security/vbs/Kconfig
+++ b/security/vbs/Kconfig
@@ -18,6 +18,7 @@ config VBS
config VBS_KVM_PLANES
bool "VBS backend: KVM software planes"
depends on VBS && KVM_GUEST
+ select VM_PLANES
help
VBS backend that uses a KVM paravirt hypercall to communicate
between plane-0 (the normal guest kernel) and plane-1 (a secure
diff --git a/security/vbs/core.c b/security/vbs/core.c
index c006b6d53a14..8dd4567bcb9c 100644
--- a/security/vbs/core.c
+++ b/security/vbs/core.c
@@ -16,6 +16,7 @@
#include <linux/reboot.h>
#include <linux/sizes.h>
#include <linux/string.h>
+#include <linux/vm_planes.h>
#include <linux/vmalloc.h>
static const struct vbs_ops *vbs_backend;
@@ -154,6 +155,17 @@ static int __init vbs_enable(void)
if (!vbs_plane_config_present())
return 0;
+ /*
+ * Create and activate the secure plane before the backend issues its
+ * first VTL call. A failure here leaves the backend idle.
+ */
+ ret = vm_planes_bootstrap();
+ if (ret) {
+ pr_warn("vbs: plane bootstrap failed (%d); backend \"%s\" left idle\n",
+ ret, ops->name);
+ return 0;
+ }
+
if (ops->init) {
ret = ops->init();
if (ret) {
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [RFC PATCH v2 8/8] drivers/virt: add KVM VM-planes secure-plane monitor
2026-08-11 1:52 [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes Sriram Nambakam
` (6 preceding siblings ...)
2026-08-11 1:52 ` [RFC PATCH v2 7/8] security/vbs: bootstrap the plane from the enable path Sriram Nambakam
@ 2026-08-11 1:52 ` Sriram Nambakam
7 siblings, 0 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
Add the secure-plane (plane >0) side of the VM-planes park/dispatch
handshake, so an otherwise ordinary kernel can act as the secure plane
without the full VBS stack. Activated by the "secure_monitor" kernel
command-line option, a late_initcall kthread hands control back to the
normal plane via KVM_HC_VBS_VTL_RETURN and then services VTL calls from the
shared calling area (matching struct vbs_kvm_ca in security/vbs).
Calls are acknowledged as no-ops for now; real per-call handlers are added
incrementally. The same option also skips the sub-1M real-mode trampoline
(arch/x86/realmode/init.c), which the secure plane neither has memory for
nor uses.
Gated by CONFIG_VBS_SECURE_MONITOR.
---
arch/x86/realmode/init.c | 21 +++++
drivers/virt/Kconfig | 15 ++++
drivers/virt/Makefile | 1 +
drivers/virt/secure_monitor.c | 141 ++++++++++++++++++++++++++++++++++
4 files changed, 178 insertions(+)
create mode 100644 drivers/virt/secure_monitor.c
diff --git a/arch/x86/realmode/init.c b/arch/x86/realmode/init.c
index 694d80a5c68e..d9d73cd892bf 100644
--- a/arch/x86/realmode/init.c
+++ b/arch/x86/realmode/init.c
@@ -44,6 +44,27 @@ 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 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. Disable the real-mode setup from an
+ * early_param so it takes effect before setup_arch() calls
+ * x86_platform.realmode_reserve(); triggered by the "secure_monitor" 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/drivers/virt/Kconfig b/drivers/virt/Kconfig
index 52eb7e4ba71f..bb1a7de559c3 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -13,6 +13,21 @@ menuconfig VIRT_DRIVERS
if VIRT_DRIVERS
+config VBS_SECURE_MONITOR
+ bool "KVM VM-planes secure-plane monitor"
+ depends on X86 && KVM_GUEST
+ help
+ 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 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"
default y
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index f29901bd7820..22d1121ba5bd 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_SECURE_MONITOR) += secure_monitor.o
obj-y += vboxguest/
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
diff --git a/drivers/virt/secure_monitor.c b/drivers/virt/secure_monitor.c
new file mode 100644
index 000000000000..4fe3ceb051e3
--- /dev/null
+++ b/drivers/virt/secure_monitor.c
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * secure_monitor - KVM VM-planes secure-plane monitor
+ *
+ * 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, though the index is not hard-coded) without pulling
+ * in the full VBS 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):
+ *
+ * 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
+ *
+ * All planes of a VM share the same memslots, so the secure plane sees the
+ * same guest-physical address space as the normal plane and can read the
+ * calling area directly. Every VTL call is acknowledged as a no-op so the
+ * normal plane can make progress; real per-call handlers 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-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/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 vbs_kvm_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 "secure_monitor" kernel command-line option. */
+static bool secmon_active __ro_after_init;
+
+static int __init secmon_setup(char *str)
+{
+ secmon_active = true;
+ return 1;
+}
+__setup("secure_monitor", 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);
+}
+
+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;
+
+ /* 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;
+ }
+
+ /*
+ * 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);
+ ca->status = 0;
+ ca->resp_size = 0;
+ status = 0;
+
+ 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] 9+ messages in thread