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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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