* [RFC PATCH v2 0/8] VBS/VSM-on-KVM: guest support using VM Planes
@ 2026-08-11 1:52 Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 1/8] KVM: x86: raise the default maximum planes to two Sriram Nambakam
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Sriram Nambakam @ 2026-08-11 1:52 UTC (permalink / raw)
To: kvm; +Cc: linux-kernel
This RFC adds guest-side Virtualization-Based Security (VBS) support using
KVM VM Planes. Plane 0 runs the normal guest kernel and plane 1 runs a
higher-trust secure kernel that receives VTL calls through the KVM
hypercall-mediated plane switch.
The series enables a second software plane by default, introduces the VBS
core and KVM backend, bootstraps the secure plane, and adds the secure-side
monitor and park/dispatch handshake.
This is v2 of the series. Based on feedback from maintainers, the KVM
host, guest, and QEMU changes are now being posted as three separate,
layered RFCs to make it easier to understand the context of these
changes as they pertain to the KVM host, guest, and QEMU. This
patchset does not yet include memory protection between planes; that
will be added in a future version.
This series is based on the KVM VM Planes prerequisite patchset obtained
from Joerg Roedel's tree (kvm-planes-v7.1):
https://github.com/joergroedel/linux/tree/kvm-planes-v7.1
This series depends on the separately posted four-patch KVM host RFC ending
at commit 6cbd0b038cb3 ("KVM: x86: handle VBS VTL call/return via in-kernel
plane switch"):
https://lore.kernel.org/kvm/20260811003114.30107-1-snambakam@linux.microsoft.com/
This remains prototype code and is not intended for production use.
The implementation and integration tooling are available at:
Linux and KVM support:
https://github.com/safe-tee/linux/tree/vm-planes-layered
QEMU support:
https://github.com/safe-tee/qemu/tree/vm-planes-layered
Build, test, and integration tooling:
https://github.com/safe-tee/lvbs/tree/vm-planes-layered
Acknowledgments
===============
This work stands on top of, and is indebted to, several prior efforts:
- Joerg Roedel, whose QEMU and KVM VM Planes work provides the
infrastructure that the VBS/VSM secure plane relies on.
- Paolo Bonzini, whose "[RFC PATCH 00/29] KVM: VM planes" introduced
the VM Plane concept to KVM as a common in-kernel model for AMD VMPLs,
Intel TDX partitions, Hyper-V VTLs, and Arm CCA planes.
https://lwn.net/Articles/1016113/
- James Bottomley and James Morris, for their ongoing VSM-on-KVM work,
which informed the design and direction of this series.
Feedback on the VBS framework, guest bootstrap, backend interface, and
normal-plane/secure-plane division is welcome.
Sriram Nambakam (8):
KVM: x86: raise the default maximum planes to two
security/vbs: introduce core VBS framework
security/vbs: add platform probe and backend registration
security/vbs: add KVM software planes backend
security/vbs: enable the backend after driver init
vm_planes: add hypervisor-assisted plane bootstrap
security/vbs: bootstrap the plane from the enable path
drivers/virt: add KVM VM-planes secure-plane monitor
arch/x86/kernel/cpu/common.c | 64 ++++
arch/x86/kvm/x86.c | 3 +-
arch/x86/realmode/init.c | 21 ++
drivers/virt/Kconfig | 15 +
drivers/virt/Makefile | 1 +
drivers/virt/secure_monitor.c | 141 ++++++++
include/linux/vbs.h | 74 ++++
include/linux/vm_planes.h | 46 +++
init/Kconfig | 18 +
init/Makefile | 4 +
init/vm_planes.c | 636 ++++++++++++++++++++++++++++++++++
security/Kconfig | 2 +
security/Makefile | 1 +
security/vbs/Kconfig | 32 ++
security/vbs/Makefile | 7 +
security/vbs/core.c | 182 ++++++++++
security/vbs/internal.h | 20 ++
security/vbs/kvm_planes.c | 177 ++++++++++
security/vbs/probe.c | 61 ++++
19 files changed, 1504 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/secure_monitor.c
create mode 100644 include/linux/vbs.h
create mode 100644 include/linux/vm_planes.h
create mode 100644 init/vm_planes.c
create mode 100644 security/vbs/Kconfig
create mode 100644 security/vbs/Makefile
create mode 100644 security/vbs/core.c
create mode 100644 security/vbs/internal.h
create mode 100644 security/vbs/kvm_planes.c
create mode 100644 security/vbs/probe.c
base-commit: 6cbd0b038cb31db2f06fe6516f5982d5bdb50174
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [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
end of thread, other threads:[~2026-08-11 1:53 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [RFC PATCH v2 3/8] security/vbs: add platform probe and backend registration Sriram Nambakam
2026-08-11 1:52 ` [RFC PATCH v2 4/8] security/vbs: add KVM software planes backend Sriram Nambakam
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 ` [RFC PATCH v2 6/8] vm_planes: add hypervisor-assisted plane bootstrap 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).