From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from linux.microsoft.com (linux.microsoft.com [13.77.154.182]) by smtp.subspace.kernel.org (Postfix) with ESMTP id C99F943CEC7; Wed, 5 Aug 2026 11:03:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=13.77.154.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785927840; cv=none; b=p4sGIgTkyCGT7Fh2BJ+6vddNf/Q9FpehJ707TfqYWzDN+5nfZAS+wmdYdcAzu/qgy0P5CVa8aRKqR3Rdh7ehXyOo3fR5tkG8yAH1MliM8CDj6eSk+Deaj0OuT3ZNOM+FZyDBahVUjTgrekDA3GGoQ6g6+7DkWjJcx7nW0hPOytI= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785927840; c=relaxed/simple; bh=jzdjyo1cSHBrg+vBvTCJ+uT+giL04yTaNxS8M7ZI0Ho=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=kNej4EpuUx+VH7+KLKy1Yp9Oal53A5xOS9HLZRWjlH5X7ed2kSywebuqtv+gxVatVjzPA5mA6xafL6bF+CsY3zDvZ1zosRR7qdfF1yQzuSgnevfB691XdEsN5JH5cN0yTtibaNoQHclJcCctm6gzvrUcgyQDjyxkpw3tyr2g/HE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com; spf=pass smtp.mailfrom=linux.microsoft.com; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b=KClYnQBQ; arc=none smtp.client-ip=13.77.154.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linux.microsoft.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=linux.microsoft.com header.i=@linux.microsoft.com header.b="KClYnQBQ" Received: from fedora.hsd1.wa.comcast.net (unknown [52.148.140.42]) by linux.microsoft.com (Postfix) with ESMTPSA id 906A420B7169; Wed, 5 Aug 2026 04:03:37 -0700 (PDT) DKIM-Filter: OpenDKIM Filter v2.11.0 linux.microsoft.com 906A420B7169 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.microsoft.com; s=default; t=1785927817; bh=duFM8Vmsa5KFjEUjTCphJphVogV9Eyn1mCYidCnYXik=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KClYnQBQrHA2HwzBUerI61Mej1MeEKx0hNOr1mQAJiiZFBNftf0tcKXG6a5/63j/f j50s1nIWIvVJBOOw5hJYzL5fiDNLjDlcD4WRHR9ovuRXu4VVHkJ6oHbsxD8rS9+2ZV RKjBnlLu88o+yJy00rXFVD1kByAt/Rlq5jEQ5Qg0= From: Sriram Nambakam To: kvm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Subject: [RFC PATCH v1 17/42] vbs: Add module authentication via VBS/HEKI Date: Wed, 5 Aug 2026 04:02:59 -0700 Message-ID: <20260805110324.25067-18-snambakam@linux.microsoft.com> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260805110324.25067-1-snambakam@linux.microsoft.com> References: <20260805110324.25067-1-snambakam@linux.microsoft.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hook the kernel module loader to send module validation requests to the secure kernel (plane-1 / QEMU) before allowing modules to load, and to set per-section EPT permissions after module formation. kernel/module/main.c: - After add_unformed_module(): call vbs_validate_module() with the module ELF blob GPA and the kernel's own sig_ok result from module_sig_check(). If the secure side rejects, loading is aborted. - After complete_formation(): call vbs_set_module_perms() to apply EPT permissions per section (text=R+X, rodata=R, data=R+W). Failure is non-fatal to avoid breaking module loading on ioctl errors. - In free_module(): call vbs_unload_module() so the secure side can release EPT overrides for the freed module. - All hooks are guarded by vbs_available() and are no-ops when VBS is not active. security/vbs/heki.h: - Add vbs_validate_module_req with module name, ELF GPA/size, and sig_ok flag (kernel's signature verification result). - Add vbs_module_section and vbs_set_module_perms_req for per-section GPA + permissions. - Add vbs_unload_module_req for module unload notification. security/vbs/kvm_planes.c: - Implement kvm_planes_validate_module(): converts vmalloc ELF pointer to GPA, sends sig_ok flag via VBS_CALL_VALIDATE_MODULE. - Implement kvm_planes_set_module_perms(): iterates mod->mem[] array, maps each section type to VBS_MEM_* permissions (TEXT→R+X, RODATA→R, DATA→R+W), sends via VBS_CALL_SET_MODULE_PERMS. - Implement kvm_planes_unload_module(): sends module name via VBS_CALL_UNLOAD_MODULE. Signed-off-by: Sriram Nambakam --- kernel/module/main.c | 37 ++++++++++++++ security/vbs/heki.h | 46 +++++++++++++++++ security/vbs/kvm_planes.c | 102 +++++++++++++++++++++++++++++++++++--- 3 files changed, 178 insertions(+), 7 deletions(-) diff --git a/kernel/module/main.c b/kernel/module/main.c index 46dd8d25a605..2d0232fccf18 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -1418,6 +1419,11 @@ static void free_module(struct module *mod) { trace_module_free(mod); + /* Notify the secure kernel that this module is being unloaded + * so it can release any EPT permission overrides. */ + if (vbs_available()) + vbs_unload_module(mod); + codetag_unload_module(mod); mod_sysfs_teardown(mod); @@ -3472,6 +3478,22 @@ static int load_module(struct load_info *info, const char __user *uargs, if (err) goto free_module; + /* + * If VBS is available, ask the secure kernel (plane-1) to + * validate this module. We pass the module name and the + * sig_ok flag from the kernel's own signature check. + * Plane-1 can enforce additional policy (e.g., allowlist). + */ + if (vbs_available()) { + err = vbs_validate_module(info->hdr, info->len, + NULL, info->sig_ok ? 1 : 0); + if (err) { + pr_warn("vbs: module '%s' rejected by secure kernel (%ld)\n", + mod->name, err); + goto unlink_mod; + } + } + /* * We are tainting your kernel if your module gets into * the modules linked list somehow. @@ -3539,6 +3561,21 @@ static int load_module(struct load_info *info, const char __user *uargs, if (err) goto ddebug_cleanup; + /* + * If VBS is available, send the module's per-section layout + * to the secure kernel so it can enforce EPT permissions: + * text → R+X (NO_WRITE), rodata → R (NO_WRITE|NO_EXEC), + * data → R+W (no restrictions). + */ + if (vbs_available()) { + err = vbs_set_module_perms(mod); + if (err) + pr_warn("vbs: set_module_perms for %s failed (%ld)\n", + mod->name, err); + /* Non-fatal: continue loading even if protection fails */ + err = 0; + } + err = prepare_coming_module(mod); if (err) goto bug_cleanup; diff --git a/security/vbs/heki.h b/security/vbs/heki.h index fee986de351a..5b7fa92bce21 100644 --- a/security/vbs/heki.h +++ b/security/vbs/heki.h @@ -36,6 +36,52 @@ struct vbs_seal_kernel_req { __u64 cr3; /* plane-0 kernel CR3 for verification */ } __packed; +/* ── Module authentication ────────────────────────────────────────────── */ + +/* + * VBS_CALL_VALIDATE_MODULE payload — plane-0 sends the GPA of the module + * ELF blob and its appended PKCS#7 signature for plane-1 verification. + * The module blob is in guest physical memory; the secure side reads it + * directly via the GPA (no copy through the CAA page). + */ +struct vbs_validate_module_req { + char name[56]; /* module name (null-terminated) */ + __u64 elf_gpa; /* GPA of the module ELF data */ + __u64 elf_size; /* size of the ELF data (excl. signature) */ + __u32 sig_ok; /* 1 if kernel's sig check passed */ + __u32 reserved; /* padding */ +} __packed; + +/* + * Per-section descriptor for VBS_CALL_SET_MODULE_PERMS. + * Sent as an array in the CAA buffer after the module name. + */ +struct vbs_module_section { + __u64 gpa; /* section GPA (page-aligned) */ + __u64 size; /* section size (page-aligned) */ + __u32 perms; /* VBS_MEM_* permission flags */ + __u32 type; /* enum mod_mem_type */ +} __packed; + +/* + * VBS_CALL_SET_MODULE_PERMS payload — after relocation, plane-0 sends + * the per-section layout so plane-1 can set EPT permissions. + * Sections follow immediately after this header in the buffer. + */ +struct vbs_set_module_perms_req { + char name[56]; /* module name (null-terminated) */ + __u32 nr_sections; /* number of vbs_module_section entries */ + __u32 flags; /* reserved, must be 0 */ + /* struct vbs_module_section sections[]; follows in buffer */ +} __packed; + +/* + * VBS_CALL_UNLOAD_MODULE payload — module is being freed. + */ +struct vbs_unload_module_req { + char name[56]; /* module name (null-terminated) */ +} __packed; + /* ── x86-64 page table walker (for plane-1 auditing) ─────────────────── */ /* Classification of a guest-physical page based on page table walk */ diff --git a/security/vbs/kvm_planes.c b/security/vbs/kvm_planes.c index 293c960c0968..1114adfbd46c 100644 --- a/security/vbs/kvm_planes.c +++ b/security/vbs/kvm_planes.c @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -147,26 +150,111 @@ static int kvm_planes_seal_kernel(void) static int kvm_planes_validate_module(const void *elf, size_t elf_size, const void *sig, size_t sig_size) { + struct vbs_validate_module_req req = {}; + struct page *elf_page; + const Elf64_Ehdr *ehdr; + + if (!elf || !elf_size) + return -EINVAL; + /* - * Module blobs can be large — for the KVM planes backend we pass - * the physical address and size to plane-1 via the VTL call and - * let plane-1 map/read the pages directly from its EPT view. - * For now, a stub that signals "not yet implemented". + * sig_size is repurposed: 1 = kernel's own sig check passed, + * 0 = module is unsigned or sig check failed. */ + req.sig_ok = sig_size ? 1 : 0; + + /* Try to extract the module name from the ELF .modinfo section. + * For now, just use a placeholder — the name is available at + * the call site in load_module() but not passed through the + * vbs_ops interface which takes (elf, elf_size, sig, sig_size). + */ + ehdr = elf; + if (elf_size >= sizeof(*ehdr) && ehdr->e_ident[0] == 0x7f) + strscpy(req.name, "module", sizeof(req.name)); + else + strscpy(req.name, "unknown", sizeof(req.name)); + + /* Get GPA of the ELF blob */ + elf_page = vmalloc_to_page(elf); + if (elf_page) { + req.elf_gpa = page_to_phys(elf_page) + + offset_in_page(elf); + req.elf_size = elf_size; + } + + pr_debug("vbs-kvm: validate_module elf_gpa=0x%llx size=0x%llx sig_ok=%u\n", + req.elf_gpa, req.elf_size, req.sig_ok); + return kvm_planes_vtl_call(VBS_CALL_VALIDATE_MODULE, - NULL, 0, NULL, 0); + &req, sizeof(req), NULL, 0); } static int kvm_planes_set_module_perms(const struct module *mod) { + struct { + struct vbs_set_module_perms_req hdr; + struct vbs_module_section sections[MOD_MEM_NUM_TYPES]; + } __packed req = {}; + int i, n = 0; + + strscpy(req.hdr.name, mod->name, sizeof(req.hdr.name)); + + for (i = 0; i < MOD_MEM_NUM_TYPES; i++) { + const struct module_memory *mem = &mod->mem[i]; + struct vbs_module_section *sec; + unsigned long gpa; + struct page *p; + + if (!mem->base || !mem->size) + continue; + + p = vmalloc_to_page(mem->base); + if (!p) + continue; + + gpa = page_to_phys(p) + offset_in_page(mem->base); + sec = &req.sections[n]; + sec->gpa = gpa; + sec->size = PAGE_ALIGN(mem->size); + sec->type = i; + + /* Set permissions based on section type */ + switch (i) { + case MOD_TEXT: + case MOD_INIT_TEXT: + sec->perms = VBS_MEM_READ | VBS_MEM_EXEC; + break; + case MOD_RODATA: + case MOD_RO_AFTER_INIT: + case MOD_INIT_RODATA: + sec->perms = VBS_MEM_READ; + break; + default: /* MOD_DATA, MOD_INIT_DATA */ + sec->perms = VBS_MEM_READ | VBS_MEM_WRITE; + break; + } + n++; + } + + req.hdr.nr_sections = n; + + pr_debug("vbs-kvm: set_module_perms %s: %d sections\n", + mod->name, n); + return kvm_planes_vtl_call(VBS_CALL_SET_MODULE_PERMS, - NULL, 0, NULL, 0); + &req, + sizeof(req.hdr) + n * sizeof(req.sections[0]), + NULL, 0); } static int kvm_planes_unload_module(const struct module *mod) { + struct vbs_unload_module_req req = {}; + + strscpy(req.name, mod->name, sizeof(req.name)); + return kvm_planes_vtl_call(VBS_CALL_UNLOAD_MODULE, - NULL, 0, NULL, 0); + &req, sizeof(req), NULL, 0); } /* ── key / certificate management ─────────────────────────────────────── */ -- 2.55.0