From patchwork Fri May 3 13:19:06 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 1931027 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; secure) header.d=digikod.net header.i=@digikod.net header.a=rsa-sha256 header.s=20191114 header.b=NvcPqXJF; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=patchwork.ozlabs.org) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VWBHk5jT4z20fb for ; Fri, 3 May 2024 23:20:18 +1000 (AEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s2sp8-0004ja-O8; Fri, 03 May 2024 09:19:39 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2sp4-0004iV-Jo for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:34 -0400 Received: from smtp-190f.mail.infomaniak.ch ([2001:1600:7:10::190f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2soy-0002gu-1W for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:34 -0400 Received: from smtp-3-0000.mail.infomaniak.ch (smtp-3-0000.mail.infomaniak.ch [10.4.36.107]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4VWBGc2g3hzS10; Fri, 3 May 2024 15:19:20 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=digikod.net; s=20191114; t=1714742360; bh=obtQipiNRMFv28P9TKvF5pV8leBuQgSBtBcEQW3pxVc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=NvcPqXJFBA2qsjuhqiLltCK88ANNHdMNpuz17Tcd63LbmHTm/KkiZOaiSif0w7f+4 cftpGAbztXjbX5M6Zeun4BIRd81twRoYYKlWzYdwI+wJzU1gYbqcjIInB4q/5H0LNP PPfyGOD1eQbV3Tnb/7BmebaPzISSc/RgHtpbrUuw= Received: from unknown by smtp-3-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 4VWBGb2zTRzbPY; Fri, 3 May 2024 15:19:19 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Ingo Molnar , Kees Cook , Paolo Bonzini , Sean Christopherson , Thomas Gleixner , Vitaly Kuznetsov , Wanpeng Li Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , "Edgecombe, Rick P" , Alexander Graf , Angelina Vu , Anna Trikalinou , Chao Peng , Forrest Yuan Yu , James Gowans , James Morris , John Andersen , "Madhavan T . Venkataraman" , Marian Rotariu , =?utf-8?q?Mihai_Don=C8=9Bu?= , =?utf-8?b?TmljdciZ?= =?utf-8?b?b3IgQ8OuyJt1?= , Thara Gopinath , Trilok Soni , Wei Liu , Will Deacon , Yu Zhang , =?utf-8?q?=C8=98tefan_=C8=98icleru?= , dev@lists.cloudhypervisor.org, kvm@vger.kernel.org, linux-hardening@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, qemu-devel@nongnu.org, virtualization@lists.linux-foundation.org, x86@kernel.org, xen-devel@lists.xenproject.org Subject: [RFC PATCH v3 1/5] virt: Introduce Hypervisor Enforced Kernel Integrity (Heki) Date: Fri, 3 May 2024 15:19:06 +0200 Message-ID: <20240503131910.307630-2-mic@digikod.net> In-Reply-To: <20240503131910.307630-1-mic@digikod.net> References: <20240503131910.307630-1-mic@digikod.net> MIME-Version: 1.0 X-Infomaniak-Routing: alpha Received-SPF: pass client-ip=2001:1600:7:10::190f; envelope-from=mic@digikod.net; helo=smtp-190f.mail.infomaniak.ch X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Madhavan T. Venkataraman Hypervisor Enforced Kernel Integrity (Heki) is a feature that will use the hypervisor to enhance guest virtual machine security. Implement minimal code to introduce Heki: - Define the config variables. - Define a kernel command line parameter "heki" to turn the feature on or off. By default, Heki is on. - Define heki_early_init() and call it in start_kernel(). Currently, this function only prints the value of the "heki" command line parameter. Cc: Borislav Petkov Cc: Dave Hansen Cc: H. Peter Anvin Cc: Ingo Molnar Cc: Kees Cook Cc: Paolo Bonzini Cc: Sean Christopherson Cc: Thomas Gleixner Cc: Vitaly Kuznetsov Cc: Wanpeng Li Co-developed-by: Mickaël Salaün Signed-off-by: Mickaël Salaün Signed-off-by: Madhavan T. Venkataraman Link: https://lore.kernel.org/r/20240503131910.307630-2-mic@digikod.net --- Changes since v2: * Move CONFIG_HEKI under a new CONFIG_HEKI_MENU to group it with the test configuration (see following patches). * Hide CONFIG_ARCH_SUPPORS_HEKI from users. Changes since v1: * Shrinked this patch to only contain the minimal common parts. * Moved heki_early_init() to start_kernel(). * Use kstrtobool(). --- Kconfig | 2 ++ arch/x86/Kconfig | 1 + include/linux/heki.h | 31 +++++++++++++++++++++++++++++++ init/main.c | 2 ++ mm/mm_init.c | 1 + virt/Makefile | 1 + virt/heki/Kconfig | 25 +++++++++++++++++++++++++ virt/heki/Makefile | 3 +++ virt/heki/common.h | 16 ++++++++++++++++ virt/heki/main.c | 33 +++++++++++++++++++++++++++++++++ 10 files changed, 115 insertions(+) create mode 100644 include/linux/heki.h create mode 100644 virt/heki/Kconfig create mode 100644 virt/heki/Makefile create mode 100644 virt/heki/common.h create mode 100644 virt/heki/main.c diff --git a/Kconfig b/Kconfig index 745bc773f567..0c844d9bcb03 100644 --- a/Kconfig +++ b/Kconfig @@ -29,4 +29,6 @@ source "lib/Kconfig" source "lib/Kconfig.debug" +source "virt/heki/Kconfig" + source "Documentation/Kconfig" diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 928820e61cb5..d2fba63c289b 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -34,6 +34,7 @@ config X86_64 select SWIOTLB select ARCH_HAS_ELFCORE_COMPAT select ZONE_DMA32 + select ARCH_SUPPORTS_HEKI config FORCE_DYNAMIC_FTRACE def_bool y diff --git a/include/linux/heki.h b/include/linux/heki.h new file mode 100644 index 000000000000..4c18d2283392 --- /dev/null +++ b/include/linux/heki.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Hypervisor Enforced Kernel Integrity (Heki) - Definitions + * + * Copyright © 2023 Microsoft Corporation + */ + +#ifndef __HEKI_H__ +#define __HEKI_H__ + +#include +#include +#include +#include +#include + +#ifdef CONFIG_HEKI + +extern bool heki_enabled; + +void heki_early_init(void); + +#else /* !CONFIG_HEKI */ + +static inline void heki_early_init(void) +{ +} + +#endif /* CONFIG_HEKI */ + +#endif /* __HEKI_H__ */ diff --git a/init/main.c b/init/main.c index 5dcf5274c09c..bec2c8d939aa 100644 --- a/init/main.c +++ b/init/main.c @@ -102,6 +102,7 @@ #include #include #include +#include #include #include @@ -1059,6 +1060,7 @@ void start_kernel(void) uts_ns_init(); key_init(); security_init(); + heki_early_init(); dbg_late_init(); net_ns_init(); vfs_caches_init(); diff --git a/mm/mm_init.c b/mm/mm_init.c index 549e76af8f82..89d9f97bd471 100644 --- a/mm/mm_init.c +++ b/mm/mm_init.c @@ -27,6 +27,7 @@ #include #include #include +#include #include "internal.h" #include "slab.h" #include "shuffle.h" diff --git a/virt/Makefile b/virt/Makefile index 1cfea9436af9..856b5ccedb5a 100644 --- a/virt/Makefile +++ b/virt/Makefile @@ -1,2 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += lib/ +obj-$(CONFIG_HEKI_MENU) += heki/ diff --git a/virt/heki/Kconfig b/virt/heki/Kconfig new file mode 100644 index 000000000000..66e73d212856 --- /dev/null +++ b/virt/heki/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Hypervisor Enforced Kernel Integrity (Heki) + +config ARCH_SUPPORTS_HEKI + bool + # An architecture should select this when it can successfully build + # and run with CONFIG_HEKI. That is, it should provide all of the + # architecture support required for the HEKI feature. + +menuconfig HEKI_MENU + bool "Virtualization hardening" + +if HEKI_MENU + +config HEKI + bool "Hypervisor Enforced Kernel Integrity (Heki)" + depends on ARCH_SUPPORTS_HEKI + help + This feature enhances guest virtual machine security by taking + advantage of security features provided by the hypervisor for guests. + This feature is helpful in maintaining guest virtual machine security + even after the guest kernel has been compromised. + +endif diff --git a/virt/heki/Makefile b/virt/heki/Makefile new file mode 100644 index 000000000000..8b10e73a154b --- /dev/null +++ b/virt/heki/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_HEKI) += main.o diff --git a/virt/heki/common.h b/virt/heki/common.h new file mode 100644 index 000000000000..edd98fc650a8 --- /dev/null +++ b/virt/heki/common.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Hypervisor Enforced Kernel Integrity (Heki) - Common header + * + * Copyright © 2023 Microsoft Corporation + */ + +#ifndef _HEKI_COMMON_H + +#ifdef pr_fmt +#undef pr_fmt +#endif + +#define pr_fmt(fmt) "heki-guest: " fmt + +#endif /* _HEKI_COMMON_H */ diff --git a/virt/heki/main.c b/virt/heki/main.c new file mode 100644 index 000000000000..25c25f5700f7 --- /dev/null +++ b/virt/heki/main.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hypervisor Enforced Kernel Integrity (Heki) - Common code + * + * Copyright © 2023 Microsoft Corporation + */ + +#include +#include + +#include "common.h" + +bool heki_enabled __ro_after_init = true; + +/* + * Must be called after kmem_cache_init(). + */ +__init void heki_early_init(void) +{ + if (!heki_enabled) { + pr_warn("Heki is not enabled\n"); + return; + } + pr_warn("Heki is enabled\n"); +} + +static int __init heki_parse_config(char *str) +{ + if (kstrtobool(str, &heki_enabled)) + pr_warn("Invalid option string for heki: '%s'\n", str); + return 1; +} +__setup("heki=", heki_parse_config); From patchwork Fri May 3 13:19:07 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 1931026 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; secure) header.d=digikod.net header.i=@digikod.net header.a=rsa-sha256 header.s=20191114 header.b=Fmwx5YE+; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=patchwork.ozlabs.org) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VWBHC5hMxz20fW for ; Fri, 3 May 2024 23:19:51 +1000 (AEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s2spC-0004kb-Cp; Fri, 03 May 2024 09:19:42 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2sp4-0004iW-Km for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:34 -0400 Received: from smtp-bc0f.mail.infomaniak.ch ([2001:1600:7:10::bc0f]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2soy-0002h1-1j for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:34 -0400 Received: from smtp-3-0000.mail.infomaniak.ch (smtp-3-0000.mail.infomaniak.ch [10.4.36.107]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4VWBGd5P4YzRYW; Fri, 3 May 2024 15:19:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=digikod.net; s=20191114; t=1714742361; bh=8e4dNhg8cJXTgu6Tduk1ij3wqe1j+rj2Pu+QmetQRF0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Fmwx5YE+iW+x7PpDaGWblZGnZb7wurP6ci4Q4GCwAcRMfopzd8vkWkcKtNJT8f84N Z/3rVqdkcn7fTd12tvbQSPZygf9ZLVEBMTWje4Ww7ynKrlLNQKpa+wyL1oCNT0eN5W u0DxKz/b4BPdXsWJpFOgkARSwFxi9QaRNlXFLyRM= Received: from unknown by smtp-3-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 4VWBGc5y1CzZ8K; Fri, 3 May 2024 15:19:20 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Ingo Molnar , Kees Cook , Paolo Bonzini , Sean Christopherson , Thomas Gleixner , Vitaly Kuznetsov , Wanpeng Li Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , "Edgecombe, Rick P" , Alexander Graf , Angelina Vu , Anna Trikalinou , Chao Peng , Forrest Yuan Yu , James Gowans , James Morris , John Andersen , "Madhavan T . Venkataraman" , Marian Rotariu , =?utf-8?q?Mihai_Don=C8=9Bu?= , =?utf-8?b?TmljdciZ?= =?utf-8?b?b3IgQ8OuyJt1?= , Thara Gopinath , Trilok Soni , Wei Liu , Will Deacon , Yu Zhang , =?utf-8?q?=C8=98tefan_=C8=98icleru?= , dev@lists.cloudhypervisor.org, kvm@vger.kernel.org, linux-hardening@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, qemu-devel@nongnu.org, virtualization@lists.linux-foundation.org, x86@kernel.org, xen-devel@lists.xenproject.org Subject: [RFC PATCH v3 2/5] KVM: x86: Add new hypercall to lock control registers Date: Fri, 3 May 2024 15:19:07 +0200 Message-ID: <20240503131910.307630-3-mic@digikod.net> In-Reply-To: <20240503131910.307630-1-mic@digikod.net> References: <20240503131910.307630-1-mic@digikod.net> MIME-Version: 1.0 X-Infomaniak-Routing: alpha Received-SPF: pass client-ip=2001:1600:7:10::bc0f; envelope-from=mic@digikod.net; helo=smtp-bc0f.mail.infomaniak.ch X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This enables guests to lock their CR0 and CR4 registers with a subset of X86_CR0_WP, X86_CR4_SMEP, X86_CR4_SMAP, X86_CR4_UMIP, X86_CR4_FSGSBASE and X86_CR4_CET flags. The new KVM_HC_LOCK_CR_UPDATE hypercall takes three arguments. The first is to identify the control register, the second is a bit mask to pin (i.e. mark as read-only), and the third is for optional flags. These register flags should already be pinned by Linux guests, but once compromised, this self-protection mechanism could be disabled, which is not the case with this dedicated hypercall. Once the CRs are pinned by the guest, if it attempts to change them, then a general protection fault is sent to the guest. This hypercall may evolve and support new kind of registers or pinning. The optional KVM_LOCK_CR_UPDATE_VERSION flag enables guests to know the supported abilities by mapping the returned version with the related features. Cc: Borislav Petkov Cc: Dave Hansen Cc: H. Peter Anvin Cc: Ingo Molnar Cc: Kees Cook Cc: Madhavan T. Venkataraman Cc: Paolo Bonzini Cc: Sean Christopherson Cc: Thomas Gleixner Cc: Vitaly Kuznetsov Cc: Wanpeng Li Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240503131910.307630-3-mic@digikod.net --- Changes since v1: * Guard KVM_HC_LOCK_CR_UPDATE hypercall with CONFIG_HEKI. * Move extern cr4_pinned_mask to x86.h (suggested by Kees Cook). * Move VMX CR checks from vmx_set_cr*() to handle_cr() to make it possible to return to user space (see next commit). * Change the heki_check_cr()'s first argument to vcpu. * Don't use -KVM_EPERM in heki_check_cr(). * Generate a fault when the guest requests a denied CR update. * Add a flags argument to get the version of this hypercall. Being able to do a preper version check was suggested by Wei Liu. --- Documentation/virt/kvm/x86/hypercalls.rst | 17 +++++ arch/x86/include/uapi/asm/kvm_para.h | 2 + arch/x86/kernel/cpu/common.c | 7 +- arch/x86/kvm/vmx/vmx.c | 5 ++ arch/x86/kvm/x86.c | 84 +++++++++++++++++++++++ arch/x86/kvm/x86.h | 22 ++++++ include/linux/kvm_host.h | 5 ++ include/uapi/linux/kvm_para.h | 1 + 8 files changed, 141 insertions(+), 2 deletions(-) diff --git a/Documentation/virt/kvm/x86/hypercalls.rst b/Documentation/virt/kvm/x86/hypercalls.rst index 10db7924720f..3178576f4c47 100644 --- a/Documentation/virt/kvm/x86/hypercalls.rst +++ b/Documentation/virt/kvm/x86/hypercalls.rst @@ -190,3 +190,20 @@ the KVM_CAP_EXIT_HYPERCALL capability. Userspace must enable that capability before advertising KVM_FEATURE_HC_MAP_GPA_RANGE in the guest CPUID. In addition, if the guest supports KVM_FEATURE_MIGRATION_CONTROL, userspace must also set up an MSR filter to process writes to MSR_KVM_MIGRATION_CONTROL. + +9. KVM_HC_LOCK_CR_UPDATE +------------------------ + +:Architecture: x86 +:Status: active +:Purpose: Request some control registers to be restricted. + +- a0: identify a control register +- a1: bit mask to make some flags read-only +- a2: optional KVM_LOCK_CR_UPDATE_VERSION flag that will return the version of + this hypercall. Version 1 supports CR0 and CR4 pinning. + +The hypercall lets a guest request control register flags to be pinned for +itself. + +Returns 0 on success or a KVM error code otherwise. diff --git a/arch/x86/include/uapi/asm/kvm_para.h b/arch/x86/include/uapi/asm/kvm_para.h index a1efa7907a0b..cfc17f3d1877 100644 --- a/arch/x86/include/uapi/asm/kvm_para.h +++ b/arch/x86/include/uapi/asm/kvm_para.h @@ -149,4 +149,6 @@ struct kvm_vcpu_pv_apf_data { #define KVM_PV_EOI_ENABLED KVM_PV_EOI_MASK #define KVM_PV_EOI_DISABLED 0x0 +#define KVM_LOCK_CR_UPDATE_VERSION (1 << 0) + #endif /* _UAPI_ASM_X86_KVM_PARA_H */ diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c index 605c26c009c8..69695d9d6e2a 100644 --- a/arch/x86/kernel/cpu/common.c +++ b/arch/x86/kernel/cpu/common.c @@ -398,8 +398,11 @@ static __always_inline void setup_umip(struct cpuinfo_x86 *c) } /* These bits should not change their value after CPU init is finished. */ -static const unsigned long cr4_pinned_mask = X86_CR4_SMEP | X86_CR4_SMAP | X86_CR4_UMIP | - X86_CR4_FSGSBASE | X86_CR4_CET | X86_CR4_FRED; +const unsigned long cr4_pinned_mask = X86_CR4_SMEP | X86_CR4_SMAP | + X86_CR4_UMIP | X86_CR4_FSGSBASE | + X86_CR4_CET | X86_CR4_FRED; +EXPORT_SYMBOL_GPL(cr4_pinned_mask); + static DEFINE_STATIC_KEY_FALSE_RO(cr_pinning); static unsigned long cr4_pinned_bits __ro_after_init; diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index 22411f4aff53..7ba970b525f7 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -5453,6 +5453,11 @@ static int handle_cr(struct kvm_vcpu *vcpu) case 0: /* mov to cr */ val = kvm_register_read(vcpu, reg); trace_kvm_cr_write(cr, val); + + ret = heki_check_cr(vcpu, cr, val); + if (ret) + return ret; + switch (cr) { case 0: err = handle_set_cr0(vcpu, val); diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index 91478b769af0..a5f47be59abc 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -8281,11 +8281,86 @@ static unsigned long emulator_get_cr(struct x86_emulate_ctxt *ctxt, int cr) return value; } +#ifdef CONFIG_HEKI + +#define HEKI_ABI_VERSION 1 + +static int heki_lock_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, + unsigned long pin, unsigned long flags) +{ + if (flags) { + if ((flags == KVM_LOCK_CR_UPDATE_VERSION) && !cr && !pin) + return HEKI_ABI_VERSION; + return -KVM_EINVAL; + } + + if (!pin) + return -KVM_EINVAL; + + switch (cr) { + case 0: + /* Cf. arch/x86/kernel/cpu/common.c */ + if (!(pin & X86_CR0_WP)) + return -KVM_EINVAL; + + if ((pin & read_cr0()) != pin) + return -KVM_EINVAL; + + atomic_long_or(pin, &vcpu->kvm->heki_pinned_cr0); + return 0; + case 4: + /* Checks for irrelevant bits. */ + if ((pin & cr4_pinned_mask) != pin) + return -KVM_EINVAL; + + /* Ignores bits not present in host. */ + pin &= __read_cr4(); + atomic_long_or(pin, &vcpu->kvm->heki_pinned_cr4); + return 0; + } + return -KVM_EINVAL; +} + +int heki_check_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, + const unsigned long val) +{ + unsigned long pinned; + + switch (cr) { + case 0: + pinned = atomic_long_read(&vcpu->kvm->heki_pinned_cr0); + if ((val & pinned) != pinned) { + pr_warn_ratelimited( + "heki: Blocked CR0 update: 0x%lx\n", val); + kvm_inject_gp(vcpu, 0); + return 1; + } + return 0; + case 4: + pinned = atomic_long_read(&vcpu->kvm->heki_pinned_cr4); + if ((val & pinned) != pinned) { + pr_warn_ratelimited( + "heki: Blocked CR4 update: 0x%lx\n", val); + kvm_inject_gp(vcpu, 0); + return 1; + } + return 0; + } + return 0; +} +EXPORT_SYMBOL_GPL(heki_check_cr); + +#endif /* CONFIG_HEKI */ + static int emulator_set_cr(struct x86_emulate_ctxt *ctxt, int cr, ulong val) { struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt); int res = 0; + res = heki_check_cr(vcpu, cr, val); + if (res) + return res; + switch (cr) { case 0: res = kvm_set_cr0(vcpu, mk_cr_64(kvm_read_cr0(vcpu), val)); @@ -10142,6 +10217,15 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) vcpu->arch.complete_userspace_io = complete_hypercall_exit; return 0; } +#ifdef CONFIG_HEKI + case KVM_HC_LOCK_CR_UPDATE: + if (a0 > U32_MAX) { + ret = -KVM_EINVAL; + } else { + ret = heki_lock_cr(vcpu, a0, a1, a2); + } + break; +#endif /* CONFIG_HEKI */ default: ret = -KVM_ENOSYS; break; diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h index a8b71803777b..ade7d68ddaff 100644 --- a/arch/x86/kvm/x86.h +++ b/arch/x86/kvm/x86.h @@ -290,6 +290,26 @@ static inline bool kvm_check_has_quirk(struct kvm *kvm, u64 quirk) return !(kvm->arch.disabled_quirks & quirk); } +#ifdef CONFIG_HEKI + +int heki_check_cr(struct kvm_vcpu *vcpu, unsigned long cr, unsigned long val); + +#else /* CONFIG_HEKI */ + +static inline int heki_check_cr(struct kvm_vcpu *vcpu, unsigned long cr, + unsigned long val) +{ + return 0; +} + +static inline int heki_lock_cr(struct kvm_vcpu *const vcpu, unsigned long cr, + unsigned long pin) +{ + return 0; +} + +#endif /* CONFIG_HEKI */ + void kvm_inject_realmode_interrupt(struct kvm_vcpu *vcpu, int irq, int inc_eip); u64 get_kvmclock_ns(struct kvm *kvm); @@ -327,6 +347,8 @@ extern u64 host_xcr0; extern u64 host_xss; extern u64 host_arch_capabilities; +extern const unsigned long cr4_pinned_mask; + extern struct kvm_caps kvm_caps; extern bool enable_pmu; diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 48f31dcd318a..6ff13937929a 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -836,6 +836,11 @@ struct kvm { bool vm_bugged; bool vm_dead; +#ifdef CONFIG_HEKI + atomic_long_t heki_pinned_cr0; + atomic_long_t heki_pinned_cr4; +#endif /* CONFIG_HEKI */ + #ifdef CONFIG_HAVE_KVM_PM_NOTIFIER struct notifier_block pm_notifier; #endif diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h index 960c7e93d1a9..2ed418704603 100644 --- a/include/uapi/linux/kvm_para.h +++ b/include/uapi/linux/kvm_para.h @@ -30,6 +30,7 @@ #define KVM_HC_SEND_IPI 10 #define KVM_HC_SCHED_YIELD 11 #define KVM_HC_MAP_GPA_RANGE 12 +#define KVM_HC_LOCK_CR_UPDATE 13 /* * hypercalls use architecture specific From patchwork Fri May 3 13:19:08 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 1931028 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; secure) header.d=digikod.net header.i=@digikod.net header.a=rsa-sha256 header.s=20191114 header.b=HFWITQU/; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=patchwork.ozlabs.org) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VWBHk4gzgz1ydX for ; Fri, 3 May 2024 23:20:18 +1000 (AEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s2spG-0004lp-Rm; Fri, 03 May 2024 09:19:46 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2sp7-0004jf-Az for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:38 -0400 Received: from smtp-8faa.mail.infomaniak.ch ([83.166.143.170]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2soy-0002h8-1W for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:37 -0400 Received: from smtp-4-0000.mail.infomaniak.ch (smtp-4-0000.mail.infomaniak.ch [10.7.10.107]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4VWBGg1gbwzQgV; Fri, 3 May 2024 15:19:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=digikod.net; s=20191114; t=1714742363; bh=UoeYf5nSk52yPkAQOPZ4IYRYsP6lzs+qv6PV8R5PQV8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HFWITQU/9X3b9ffsqT8cXmFLNi73I0373rD/PFtktQpoOH+2zmgwV5Yfw+E/F3R8K ZQMa5hgDHaXOz1twAAsinnIZ+pVrQ6lX74KgknaVZVKex/5MbuFdKHrG4JhjfAKeSk 26YP9U0qWz7UdHB+iwnqs+toDdVpb4+HaghZoTfY= Received: from unknown by smtp-4-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 4VWBGf1DjgzGSs; Fri, 3 May 2024 15:19:22 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Ingo Molnar , Kees Cook , Paolo Bonzini , Sean Christopherson , Thomas Gleixner , Vitaly Kuznetsov , Wanpeng Li Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , "Edgecombe, Rick P" , Alexander Graf , Angelina Vu , Anna Trikalinou , Chao Peng , Forrest Yuan Yu , James Gowans , James Morris , John Andersen , "Madhavan T . Venkataraman" , Marian Rotariu , =?utf-8?q?Mihai_Don=C8=9Bu?= , =?utf-8?b?TmljdciZ?= =?utf-8?b?b3IgQ8OuyJt1?= , Thara Gopinath , Trilok Soni , Wei Liu , Will Deacon , Yu Zhang , =?utf-8?q?=C8=98tefan_=C8=98icleru?= , dev@lists.cloudhypervisor.org, kvm@vger.kernel.org, linux-hardening@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, qemu-devel@nongnu.org, virtualization@lists.linux-foundation.org, x86@kernel.org, xen-devel@lists.xenproject.org Subject: [RFC PATCH v3 3/5] KVM: x86: Add notifications for Heki policy configuration and violation Date: Fri, 3 May 2024 15:19:08 +0200 Message-ID: <20240503131910.307630-4-mic@digikod.net> In-Reply-To: <20240503131910.307630-1-mic@digikod.net> References: <20240503131910.307630-1-mic@digikod.net> MIME-Version: 1.0 X-Infomaniak-Routing: alpha Received-SPF: pass client-ip=83.166.143.170; envelope-from=mic@digikod.net; helo=smtp-8faa.mail.infomaniak.ch X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, T_SPF_TEMPERROR=0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Add an interface for user space to be notified about guests' Heki policy and related violations. Extend the KVM_ENABLE_CAP IOCTL with KVM_CAP_HEKI_CONFIGURE and KVM_CAP_HEKI_DENIAL. Each one takes a bitmask as first argument that can contains KVM_HEKI_EXIT_REASON_CR0 and KVM_HEKI_EXIT_REASON_CR4. The returned value is the bitmask of known Heki exit reasons, for now: KVM_HEKI_EXIT_REASON_CR0 and KVM_HEKI_EXIT_REASON_CR4. If KVM_CAP_HEKI_CONFIGURE is set, a VM exit will be triggered for each KVM_HC_LOCK_CR_UPDATE hypercalls according to the requested control register. This enables to enlighten the VMM with the guest auto-restrictions. If KVM_CAP_HEKI_DENIAL is set, a VM exit will be triggered for each pinned CR violation. This enables the VMM to react to a policy violation. Cc: Borislav Petkov Cc: Dave Hansen Cc: H. Peter Anvin Cc: Ingo Molnar Cc: Kees Cook Cc: Madhavan T. Venkataraman Cc: Paolo Bonzini Cc: Sean Christopherson Cc: Thomas Gleixner Cc: Vitaly Kuznetsov Cc: Wanpeng Li Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240503131910.307630-4-mic@digikod.net --- Changes since v1: * New patch. Making user space aware of Heki properties was requested by Sean Christopherson. --- arch/x86/kvm/vmx/vmx.c | 5 +- arch/x86/kvm/x86.c | 114 +++++++++++++++++++++++++++++++++++---- arch/x86/kvm/x86.h | 7 +-- include/linux/kvm_host.h | 2 + include/uapi/linux/kvm.h | 22 ++++++++ 5 files changed, 136 insertions(+), 14 deletions(-) diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index 7ba970b525f7..5869a1ed7866 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -5445,6 +5445,7 @@ static int handle_cr(struct kvm_vcpu *vcpu) int reg; int err; int ret; + bool exit = false; exit_qualification = vmx_get_exit_qual(vcpu); cr = exit_qualification & 15; @@ -5454,8 +5455,8 @@ static int handle_cr(struct kvm_vcpu *vcpu) val = kvm_register_read(vcpu, reg); trace_kvm_cr_write(cr, val); - ret = heki_check_cr(vcpu, cr, val); - if (ret) + ret = heki_check_cr(vcpu, cr, val, &exit); + if (exit) return ret; switch (cr) { diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c index a5f47be59abc..865e88f2b0fc 100644 --- a/arch/x86/kvm/x86.c +++ b/arch/x86/kvm/x86.c @@ -119,6 +119,10 @@ static u64 __read_mostly cr4_reserved_bits = CR4_RESERVED_BITS; #define KVM_CAP_PMU_VALID_MASK KVM_PMU_CAP_DISABLE +#define KVM_HEKI_EXIT_REASON_VALID_MASK ( \ + KVM_HEKI_EXIT_REASON_CR0 | \ + KVM_HEKI_EXIT_REASON_CR4) + #define KVM_X2APIC_API_VALID_FLAGS (KVM_X2APIC_API_USE_32BIT_IDS | \ KVM_X2APIC_API_DISABLE_BROADCAST_QUIRK) @@ -4836,6 +4840,10 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext) if (kvm_is_vm_type_supported(KVM_X86_SW_PROTECTED_VM)) r |= BIT(KVM_X86_SW_PROTECTED_VM); break; + case KVM_CAP_HEKI_CONFIGURE: + case KVM_CAP_HEKI_DENIAL: + r = KVM_HEKI_EXIT_REASON_VALID_MASK; + break; default: break; } @@ -6729,6 +6737,22 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm, } mutex_unlock(&kvm->lock); break; +#ifdef CONFIG_HEKI + case KVM_CAP_HEKI_CONFIGURE: + r = -EINVAL; + if (cap->args[0] & ~KVM_HEKI_EXIT_REASON_VALID_MASK) + break; + kvm->heki_configure_exit_reason = cap->args[0]; + r = 0; + break; + case KVM_CAP_HEKI_DENIAL: + r = -EINVAL; + if (cap->args[0] & ~KVM_HEKI_EXIT_REASON_VALID_MASK) + break; + kvm->heki_denial_exit_reason = cap->args[0]; + r = 0; + break; +#endif default: r = -EINVAL; break; @@ -8283,11 +8307,60 @@ static unsigned long emulator_get_cr(struct x86_emulate_ctxt *ctxt, int cr) #ifdef CONFIG_HEKI +static int complete_heki_configure_exit(struct kvm_vcpu *const vcpu) +{ + kvm_rax_write(vcpu, 0); + ++vcpu->stat.hypercalls; + return kvm_skip_emulated_instruction(vcpu); +} + +static int complete_heki_denial_exit(struct kvm_vcpu *const vcpu) +{ + kvm_inject_gp(vcpu, 0); + return 1; +} + +/* Returns true if the @exit_reason is handled by @vcpu->kvm. */ +static bool heki_exit_cr(struct kvm_vcpu *const vcpu, const __u32 exit_reason, + const u64 heki_reason, unsigned long value) +{ + switch (exit_reason) { + case KVM_EXIT_HEKI_CONFIGURE: + if (!(vcpu->kvm->heki_configure_exit_reason & heki_reason)) + return false; + + vcpu->run->heki_configure.reason = heki_reason; + memset(vcpu->run->heki_configure.reserved, 0, + sizeof(vcpu->run->heki_configure.reserved)); + vcpu->run->heki_configure.cr_pinned = value; + vcpu->arch.complete_userspace_io = complete_heki_configure_exit; + break; + case KVM_EXIT_HEKI_DENIAL: + if (!(vcpu->kvm->heki_denial_exit_reason & heki_reason)) + return false; + + vcpu->run->heki_denial.reason = heki_reason; + memset(vcpu->run->heki_denial.reserved, 0, + sizeof(vcpu->run->heki_denial.reserved)); + vcpu->run->heki_denial.cr_value = value; + vcpu->arch.complete_userspace_io = complete_heki_denial_exit; + break; + default: + WARN_ON_ONCE(1); + return false; + } + + vcpu->run->exit_reason = exit_reason; + return true; +} + #define HEKI_ABI_VERSION 1 static int heki_lock_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, - unsigned long pin, unsigned long flags) + unsigned long pin, unsigned long flags, bool *exit) { + *exit = false; + if (flags) { if ((flags == KVM_LOCK_CR_UPDATE_VERSION) && !cr && !pin) return HEKI_ABI_VERSION; @@ -8307,6 +8380,8 @@ static int heki_lock_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, return -KVM_EINVAL; atomic_long_or(pin, &vcpu->kvm->heki_pinned_cr0); + *exit = heki_exit_cr(vcpu, KVM_EXIT_HEKI_CONFIGURE, + KVM_HEKI_EXIT_REASON_CR0, pin); return 0; case 4: /* Checks for irrelevant bits. */ @@ -8316,24 +8391,37 @@ static int heki_lock_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, /* Ignores bits not present in host. */ pin &= __read_cr4(); atomic_long_or(pin, &vcpu->kvm->heki_pinned_cr4); + *exit = heki_exit_cr(vcpu, KVM_EXIT_HEKI_CONFIGURE, + KVM_HEKI_EXIT_REASON_CR4, pin); return 0; } return -KVM_EINVAL; } +/* + * Sets @exit to true if the caller must exit (i.e. denied access) with the + * returned value: + * - 0 when kvm_run is configured; + * - 1 when there is no user space handler. + */ int heki_check_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, - const unsigned long val) + const unsigned long val, bool *exit) { unsigned long pinned; + *exit = false; + switch (cr) { case 0: pinned = atomic_long_read(&vcpu->kvm->heki_pinned_cr0); if ((val & pinned) != pinned) { pr_warn_ratelimited( "heki: Blocked CR0 update: 0x%lx\n", val); - kvm_inject_gp(vcpu, 0); - return 1; + *exit = true; + if (heki_exit_cr(vcpu, KVM_EXIT_HEKI_DENIAL, + KVM_HEKI_EXIT_REASON_CR0, val)) + return 0; + return complete_heki_denial_exit(vcpu); } return 0; case 4: @@ -8341,8 +8429,11 @@ int heki_check_cr(struct kvm_vcpu *const vcpu, const unsigned long cr, if ((val & pinned) != pinned) { pr_warn_ratelimited( "heki: Blocked CR4 update: 0x%lx\n", val); - kvm_inject_gp(vcpu, 0); - return 1; + *exit = true; + if (heki_exit_cr(vcpu, KVM_EXIT_HEKI_DENIAL, + KVM_HEKI_EXIT_REASON_CR4, val)) + return 0; + return complete_heki_denial_exit(vcpu); } return 0; } @@ -8356,9 +8447,10 @@ static int emulator_set_cr(struct x86_emulate_ctxt *ctxt, int cr, ulong val) { struct kvm_vcpu *vcpu = emul_to_vcpu(ctxt); int res = 0; + bool exit = false; - res = heki_check_cr(vcpu, cr, val); - if (res) + res = heki_check_cr(vcpu, cr, val, &exit); + if (exit) return res; switch (cr) { @@ -10222,7 +10314,11 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu) if (a0 > U32_MAX) { ret = -KVM_EINVAL; } else { - ret = heki_lock_cr(vcpu, a0, a1, a2); + bool exit = false; + + ret = heki_lock_cr(vcpu, a0, a1, a2, &exit); + if (exit) + return ret; } break; #endif /* CONFIG_HEKI */ diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h index ade7d68ddaff..2740b74ab583 100644 --- a/arch/x86/kvm/x86.h +++ b/arch/x86/kvm/x86.h @@ -292,18 +292,19 @@ static inline bool kvm_check_has_quirk(struct kvm *kvm, u64 quirk) #ifdef CONFIG_HEKI -int heki_check_cr(struct kvm_vcpu *vcpu, unsigned long cr, unsigned long val); +int heki_check_cr(struct kvm_vcpu *vcpu, unsigned long cr, unsigned long val, + bool *exit); #else /* CONFIG_HEKI */ static inline int heki_check_cr(struct kvm_vcpu *vcpu, unsigned long cr, - unsigned long val) + unsigned long val, bool *exit) { return 0; } static inline int heki_lock_cr(struct kvm_vcpu *const vcpu, unsigned long cr, - unsigned long pin) + unsigned long pin, bool *exit) { return 0; } diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h index 6ff13937929a..cf8e271d47aa 100644 --- a/include/linux/kvm_host.h +++ b/include/linux/kvm_host.h @@ -839,6 +839,8 @@ struct kvm { #ifdef CONFIG_HEKI atomic_long_t heki_pinned_cr0; atomic_long_t heki_pinned_cr4; + u64 heki_configure_exit_reason; + u64 heki_denial_exit_reason; #endif /* CONFIG_HEKI */ #ifdef CONFIG_HAVE_KVM_PM_NOTIFIER diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h index 2190adbe3002..1051c2f817ba 100644 --- a/include/uapi/linux/kvm.h +++ b/include/uapi/linux/kvm.h @@ -178,6 +178,8 @@ struct kvm_xen_exit { #define KVM_EXIT_NOTIFY 37 #define KVM_EXIT_LOONGARCH_IOCSR 38 #define KVM_EXIT_MEMORY_FAULT 39 +#define KVM_EXIT_HEKI_CONFIGURE 40 +#define KVM_EXIT_HEKI_DENIAL 41 /* For KVM_EXIT_INTERNAL_ERROR */ /* Emulate instruction failed. */ @@ -433,6 +435,24 @@ struct kvm_run { __u64 gpa; __u64 size; } memory_fault; + /* KVM_EXIT_HEKI_CONFIGURE */ + struct { +#define KVM_HEKI_EXIT_REASON_CR0 (1ULL << 0) +#define KVM_HEKI_EXIT_REASON_CR4 (1ULL << 1) + __u64 reason; + union { + __u64 cr_pinned; + __u64 reserved[7]; /* ignored */ + }; + } heki_configure; + /* KVM_EXIT_HEKI_DENIAL */ + struct { + __u64 reason; + union { + __u64 cr_value; + __u64 reserved[7]; /* ignored */ + }; + } heki_denial; /* Fix the size of the union. */ char padding[256]; }; @@ -917,6 +937,8 @@ struct kvm_enable_cap { #define KVM_CAP_MEMORY_ATTRIBUTES 233 #define KVM_CAP_GUEST_MEMFD 234 #define KVM_CAP_VM_TYPES 235 +#define KVM_CAP_HEKI_CONFIGURE 236 +#define KVM_CAP_HEKI_DENIAL 237 struct kvm_irq_routing_irqchip { __u32 irqchip; From patchwork Fri May 3 13:19:09 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 1931031 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; secure) header.d=digikod.net header.i=@digikod.net header.a=rsa-sha256 header.s=20191114 header.b=YqIvlgiE; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=patchwork.ozlabs.org) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VWBJX1kcxz20fb for ; Fri, 3 May 2024 23:21:00 +1000 (AEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s2sp6-0004ix-1x; Fri, 03 May 2024 09:19:36 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2sp2-0004hC-25 for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:32 -0400 Received: from smtp-190e.mail.infomaniak.ch ([185.125.25.14]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2soy-0002hc-1l for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:31 -0400 Received: from smtp-3-0000.mail.infomaniak.ch (smtp-3-0000.mail.infomaniak.ch [10.4.36.107]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4VWBGh4fbkzS3b; Fri, 3 May 2024 15:19:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=digikod.net; s=20191114; t=1714742364; bh=5C2oJwkaABGy0N3TgDchTsaJRP0NaN1sG1zefdnqG5Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YqIvlgiE19ek2V9SMYQfmDo9Wa87WmiGMnbGB2jn2HxiItp8TNw5d9eeeBTU8LpK3 AZT0MKxeoxXmSYrZ0EETcC/IslnkqX1RyIwnBoKBpYfJ00vOsRqigZFHpCXr5dOfgY YVXF5SDClUmkftFfBhN3Uo9dR3fSNJeaCw/yx6WM= Received: from unknown by smtp-3-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 4VWBGg5Ql1zZFN; Fri, 3 May 2024 15:19:23 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Ingo Molnar , Kees Cook , Paolo Bonzini , Sean Christopherson , Thomas Gleixner , Vitaly Kuznetsov , Wanpeng Li Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , "Edgecombe, Rick P" , Alexander Graf , Angelina Vu , Anna Trikalinou , Chao Peng , Forrest Yuan Yu , James Gowans , James Morris , John Andersen , "Madhavan T . Venkataraman" , Marian Rotariu , =?utf-8?q?Mihai_Don=C8=9Bu?= , =?utf-8?b?TmljdciZ?= =?utf-8?b?b3IgQ8OuyJt1?= , Thara Gopinath , Trilok Soni , Wei Liu , Will Deacon , Yu Zhang , =?utf-8?q?=C8=98tefan_=C8=98icleru?= , dev@lists.cloudhypervisor.org, kvm@vger.kernel.org, linux-hardening@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, qemu-devel@nongnu.org, virtualization@lists.linux-foundation.org, x86@kernel.org, xen-devel@lists.xenproject.org Subject: [RFC PATCH v3 4/5] heki: Lock guest control registers at the end of guest kernel init Date: Fri, 3 May 2024 15:19:09 +0200 Message-ID: <20240503131910.307630-5-mic@digikod.net> In-Reply-To: <20240503131910.307630-1-mic@digikod.net> References: <20240503131910.307630-1-mic@digikod.net> MIME-Version: 1.0 X-Infomaniak-Routing: alpha Received-SPF: pass client-ip=185.125.25.14; envelope-from=mic@digikod.net; helo=smtp-190e.mail.infomaniak.ch X-Spam_score_int: -27 X-Spam_score: -2.8 X-Spam_bar: -- X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org The hypervisor needs to provide some functions to support Heki. These form the Heki-Hypervisor API. Define a heki_hypervisor structure to house the API functions. A hypervisor that supports Heki must instantiate a heki_hypervisor structure and pass it to the Heki common code. This allows the common code to access these functions in a hypervisor-agnostic way. The first function that is implemented is lock_crs() (lock control registers). That is, certain flags in the control registers are pinned so that they can never be changed for the lifetime of the guest. Implement Heki support in the guest: - Each supported hypervisor in x86 implements a set of functions for the guest kernel. Add an init_heki() function to that set. This function initializes Heki-related stuff. Call init_heki() for the detected hypervisor in init_hypervisor_platform(). - Implement init_heki() for the guest. - Implement kvm_lock_crs() in the guest to lock down control registers. This function calls a KVM hypercall to do the job. - Instantiate a heki_hypervisor structure that contains a pointer to kvm_lock_crs(). - Pass the heki_hypervisor structure to Heki common code in init_heki(). Implement a heki_late_init() function and call it at the end of kernel init. This function calls lock_crs(). In other words, control registers of a guest are locked down at the end of guest kernel init. Cc: Borislav Petkov Cc: Dave Hansen Cc: H. Peter Anvin Cc: Ingo Molnar Cc: Kees Cook Cc: Paolo Bonzini Cc: Sean Christopherson Cc: Thomas Gleixner Cc: Vitaly Kuznetsov Cc: Wanpeng Li Co-developed-by: Madhavan T. Venkataraman Signed-off-by: Madhavan T. Venkataraman Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240503131910.307630-5-mic@digikod.net --- Changes since v2: * Hide CONFIG_HYPERVISOR_SUPPORTS_HEKI from users. Changes since v1: * Shrinked the patch to only manage the CR pinning. --- arch/x86/include/asm/x86_init.h | 1 + arch/x86/kernel/cpu/hypervisor.c | 1 + arch/x86/kernel/kvm.c | 56 ++++++++++++++++++++++++++++++++ arch/x86/kvm/Kconfig | 1 + include/linux/heki.h | 22 +++++++++++++ init/main.c | 1 + virt/heki/Kconfig | 8 ++++- virt/heki/main.c | 25 ++++++++++++++ 8 files changed, 114 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/x86_init.h b/arch/x86/include/asm/x86_init.h index 6149eabe200f..113998799473 100644 --- a/arch/x86/include/asm/x86_init.h +++ b/arch/x86/include/asm/x86_init.h @@ -128,6 +128,7 @@ struct x86_hyper_init { bool (*msi_ext_dest_id)(void); void (*init_mem_mapping)(void); void (*init_after_bootmem)(void); + void (*init_heki)(void); }; /** diff --git a/arch/x86/kernel/cpu/hypervisor.c b/arch/x86/kernel/cpu/hypervisor.c index 553bfbfc3a1b..6085c8129e0c 100644 --- a/arch/x86/kernel/cpu/hypervisor.c +++ b/arch/x86/kernel/cpu/hypervisor.c @@ -106,4 +106,5 @@ void __init init_hypervisor_platform(void) x86_hyper_type = h->type; x86_init.hyper.init_platform(); + x86_init.hyper.init_heki(); } diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c index 7f0732bc0ccd..a54f2c0d7cd0 100644 --- a/arch/x86/kernel/kvm.c +++ b/arch/x86/kernel/kvm.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -999,6 +1000,60 @@ static bool kvm_sev_es_hcall_finish(struct ghcb *ghcb, struct pt_regs *regs) } #endif +#ifdef CONFIG_HEKI + +extern unsigned long cr4_pinned_mask; + +/* + * TODO: Check SMP policy consistency, e.g. with + * this_cpu_read(cpu_tlbstate.cr4) + */ +static int kvm_lock_crs(void) +{ + unsigned long cr4; + int err; + + err = kvm_hypercall3(KVM_HC_LOCK_CR_UPDATE, 0, X86_CR0_WP, 0); + if (err) + return err; + + cr4 = __read_cr4(); + err = kvm_hypercall3(KVM_HC_LOCK_CR_UPDATE, 4, cr4 & cr4_pinned_mask, + 0); + return err; +} + +static struct heki_hypervisor kvm_heki_hypervisor = { + .lock_crs = kvm_lock_crs, +}; + +static void kvm_init_heki(void) +{ + long err; + + if (!kvm_para_available()) { + /* Cannot make KVM hypercalls. */ + return; + } + + err = kvm_hypercall3(KVM_HC_LOCK_CR_UPDATE, 0, 0, + KVM_LOCK_CR_UPDATE_VERSION); + if (err < 1) { + /* Ignores host not supporting at least the first version. */ + return; + } + + heki.hypervisor = &kvm_heki_hypervisor; +} + +#else /* CONFIG_HEKI */ + +static void kvm_init_heki(void) +{ +} + +#endif /* CONFIG_HEKI */ + const __initconst struct hypervisor_x86 x86_hyper_kvm = { .name = "KVM", .detect = kvm_detect, @@ -1007,6 +1062,7 @@ const __initconst struct hypervisor_x86 x86_hyper_kvm = { .init.x2apic_available = kvm_para_available, .init.msi_ext_dest_id = kvm_msi_ext_dest_id, .init.init_platform = kvm_init_platform, + .init.init_heki = kvm_init_heki, #if defined(CONFIG_AMD_MEM_ENCRYPT) .runtime.sev_es_hcall_prepare = kvm_sev_es_hcall_prepare, .runtime.sev_es_hcall_finish = kvm_sev_es_hcall_finish, diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig index 0ebdd088f28b..68e0e8d7230a 100644 --- a/arch/x86/kvm/Kconfig +++ b/arch/x86/kvm/Kconfig @@ -44,6 +44,7 @@ config KVM select KVM_VFIO select HAVE_KVM_PM_NOTIFIER if PM select KVM_GENERIC_HARDWARE_ENABLING + select HYPERVISOR_SUPPORTS_HEKI help Support hosting fully virtualized guest machines using hardware virtualization extensions. You will need a fairly recent diff --git a/include/linux/heki.h b/include/linux/heki.h index 4c18d2283392..96ccb17657e5 100644 --- a/include/linux/heki.h +++ b/include/linux/heki.h @@ -9,6 +9,7 @@ #define __HEKI_H__ #include +#include #include #include #include @@ -16,15 +17,36 @@ #ifdef CONFIG_HEKI +/* + * A hypervisor that supports Heki will instantiate this structure to + * provide hypervisor specific functions for Heki. + */ +struct heki_hypervisor { + int (*lock_crs)(void); /* Lock control registers. */ +}; + +/* + * If the active hypervisor supports Heki, it will plug its heki_hypervisor + * pointer into this heki structure. + */ +struct heki { + struct heki_hypervisor *hypervisor; +}; + +extern struct heki heki; extern bool heki_enabled; void heki_early_init(void); +void heki_late_init(void); #else /* !CONFIG_HEKI */ static inline void heki_early_init(void) { } +static inline void heki_late_init(void) +{ +} #endif /* CONFIG_HEKI */ diff --git a/init/main.c b/init/main.c index bec2c8d939aa..c2dc663ab4b5 100644 --- a/init/main.c +++ b/init/main.c @@ -1454,6 +1454,7 @@ static int __ref kernel_init(void *unused) exit_boot_config(); free_initmem(); mark_readonly(); + heki_late_init(); /* * Kernel mappings are now finalized - update the userspace page-table diff --git a/virt/heki/Kconfig b/virt/heki/Kconfig index 66e73d212856..0c764e342f48 100644 --- a/virt/heki/Kconfig +++ b/virt/heki/Kconfig @@ -8,6 +8,12 @@ config ARCH_SUPPORTS_HEKI # and run with CONFIG_HEKI. That is, it should provide all of the # architecture support required for the HEKI feature. +config HYPERVISOR_SUPPORTS_HEKI + bool + # A hypervisor should select this when it can successfully build + # and run with CONFIG_HEKI. That is, it should provide all of the + # hypervisor support required for the Heki feature. + menuconfig HEKI_MENU bool "Virtualization hardening" @@ -15,7 +21,7 @@ if HEKI_MENU config HEKI bool "Hypervisor Enforced Kernel Integrity (Heki)" - depends on ARCH_SUPPORTS_HEKI + depends on ARCH_SUPPORTS_HEKI && HYPERVISOR_SUPPORTS_HEKI help This feature enhances guest virtual machine security by taking advantage of security features provided by the hypervisor for guests. diff --git a/virt/heki/main.c b/virt/heki/main.c index 25c25f5700f7..ef0530a03e09 100644 --- a/virt/heki/main.c +++ b/virt/heki/main.c @@ -11,6 +11,7 @@ #include "common.h" bool heki_enabled __ro_after_init = true; +struct heki heki; /* * Must be called after kmem_cache_init(). @@ -22,6 +23,30 @@ __init void heki_early_init(void) return; } pr_warn("Heki is enabled\n"); + + if (!heki.hypervisor) { + /* This happens for kernels running on bare metal as well. */ + pr_warn("No support for Heki in the active hypervisor\n"); + return; + } + pr_warn("Heki is supported by the active Hypervisor\n"); +} + +/* + * Must be called after mark_readonly(). + */ +void heki_late_init(void) +{ + struct heki_hypervisor *hypervisor = heki.hypervisor; + + if (!heki_enabled || !heki.hypervisor) + return; + + /* Locks control registers so a compromised guest cannot change them. */ + if (WARN_ON(hypervisor->lock_crs())) + return; + + pr_warn("Control registers locked\n"); } static int __init heki_parse_config(char *str) From patchwork Fri May 3 13:19:10 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 1931029 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; secure) header.d=digikod.net header.i=@digikod.net header.a=rsa-sha256 header.s=20191114 header.b=KMEY6pkE; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org (client-ip=209.51.188.17; helo=lists.gnu.org; envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org; receiver=patchwork.ozlabs.org) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4VWBJV0YRpz1ydX for ; Fri, 3 May 2024 23:20:58 +1000 (AEST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1s2spO-0004wL-M4; Fri, 03 May 2024 09:19:54 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2sp7-0004jg-EH for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:38 -0400 Received: from smtp-42ad.mail.infomaniak.ch ([84.16.66.173]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1s2soz-0002hp-7b for qemu-devel@nongnu.org; Fri, 03 May 2024 09:19:37 -0400 Received: from smtp-3-0001.mail.infomaniak.ch (smtp-3-0001.mail.infomaniak.ch [10.4.36.108]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4VWBGk08rZzRxX; Fri, 3 May 2024 15:19:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=digikod.net; s=20191114; t=1714742365; bh=4ONNBgWSam9aJRjdro/4dfXdqBvgud7e8GDkEnOey/s=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KMEY6pkEAKgoExfEmKVQNyCUEruknH1ap7Ou5Y419jIaRi8bmkoHU2rC0/FMv7d7x rFR+BO0giuToCc4a3wUUdcvtmQ+cooljaMVQXO/XQSIZria32QbPa/X8LojG8X9+C1 h838ru/DHL68SkgW/jHR0XIi48e+6J+Z2qvxBa4k= Received: from unknown by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4VWBGj1GSJzcWL; Fri, 3 May 2024 15:19:25 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Borislav Petkov , Dave Hansen , "H . Peter Anvin" , Ingo Molnar , Kees Cook , Paolo Bonzini , Sean Christopherson , Thomas Gleixner , Vitaly Kuznetsov , Wanpeng Li Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , "Edgecombe, Rick P" , Alexander Graf , Angelina Vu , Anna Trikalinou , Chao Peng , Forrest Yuan Yu , James Gowans , James Morris , John Andersen , "Madhavan T . Venkataraman" , Marian Rotariu , =?utf-8?q?Mihai_Don=C8=9Bu?= , =?utf-8?b?TmljdciZ?= =?utf-8?b?b3IgQ8OuyJt1?= , Thara Gopinath , Trilok Soni , Wei Liu , Will Deacon , Yu Zhang , =?utf-8?q?=C8=98tefan_=C8=98icleru?= , dev@lists.cloudhypervisor.org, kvm@vger.kernel.org, linux-hardening@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, qemu-devel@nongnu.org, virtualization@lists.linux-foundation.org, x86@kernel.org, xen-devel@lists.xenproject.org Subject: [RFC PATCH v3 5/5] virt: Add Heki KUnit tests Date: Fri, 3 May 2024 15:19:10 +0200 Message-ID: <20240503131910.307630-6-mic@digikod.net> In-Reply-To: <20240503131910.307630-1-mic@digikod.net> References: <20240503131910.307630-1-mic@digikod.net> MIME-Version: 1.0 X-Infomaniak-Routing: alpha Received-SPF: pass client-ip=84.16.66.173; envelope-from=mic@digikod.net; helo=smtp-42ad.mail.infomaniak.ch X-Spam_score_int: -27 X-Spam_score: -2.8 X-Spam_bar: -- X-Spam_report: (-2.8 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org The new CONFIG_HEKI_KUNIT_TEST option enables to run tests in a a kernel module. The minimal required configuration is listed in the virt/heki-test/.kunitconfig file. test_cr_disable_smep checks control-register pinning by trying to disable SMEP. This test should then failed on a non-protected kernel, and only succeed with a kernel protected by Heki. This test doesn't rely on native_write_cr4() because of the cr4_pinned_mask hardening, which means that this *test* module loads a valid kernel code to arbitrary change CR4. This simulate an attack scenario where an attaker would use ROP to directly jump to the related cr4 instruction. As for any KUnit test, the kernel is tainted with TAINT_TEST when the test is executed. It is interesting to create new KUnit tests instead of extending KVM's Kselftests because Heki is design to be hypervisor-agnostic, it relies on a set of hypercalls (for KVM or others), and we also want to test kernel's configuration (actual pinned CR). However, new KVM's Kselftests would be useful to test KVM's interface with the host. When using Qemu, we need to pass the following arguments: -cpu host -enable-kvm For now, it is not possible to run these tests as built-in but we are working on that [1]. If tests are built-in anyway, they will just be skipped because Heki would not be enabled. Run Heki tests with: insmod heki-test.ko KTAP version 1 1..1 KTAP version 1 # Subtest: heki_x86 # module: heki_test 1..1 ok 1 test_cr_disable_smep ok 1 heki_x86 Link: https://lore.kernel.org/r/20240229170409.365386-2-mic@digikod.net [1] Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240503131910.307630-6-mic@digikod.net --- Changes since v2: * Make tests standalone (e.g. don't depends on CONFIG_HEKI). * Enable to create a test kernel module. * Don't rely on private kernel symbols. * Handle GP fault for CR-pinning test case. * Rename option to CONFIG_HEKI_KUNIT_TEST. * Add the list of required kernel options. * Move tests to virt/heki-test/ [FIXME] * Only keep CR pinning test. * Restore previous state (with SMEP enabled). * Add a Kconfig menu for Heki and update the description. * Skip tests if Heki is not protecting the running kernel. Changes since v1: * Move all tests to virt/heki/tests.c --- include/linux/heki.h | 1 + virt/heki/.kunitconfig | 9 ++++ virt/heki/Kconfig | 12 +++++ virt/heki/Makefile | 1 + virt/heki/heki-test.c | 114 +++++++++++++++++++++++++++++++++++++++++ virt/heki/main.c | 10 ++++ 6 files changed, 147 insertions(+) create mode 100644 virt/heki/.kunitconfig create mode 100644 virt/heki/heki-test.c diff --git a/include/linux/heki.h b/include/linux/heki.h index 96ccb17657e5..3294c4d583e5 100644 --- a/include/linux/heki.h +++ b/include/linux/heki.h @@ -35,6 +35,7 @@ struct heki { extern struct heki heki; extern bool heki_enabled; +extern bool heki_enforcing; void heki_early_init(void); void heki_late_init(void); diff --git a/virt/heki/.kunitconfig b/virt/heki/.kunitconfig new file mode 100644 index 000000000000..ad4454800579 --- /dev/null +++ b/virt/heki/.kunitconfig @@ -0,0 +1,9 @@ +CONFIG_HEKI=y +CONFIG_HEKI_KUNIT_TEST=m +CONFIG_HEKI_MENU=y +CONFIG_HIGH_RES_TIMERS=y +CONFIG_HYPERVISOR_GUEST=y +CONFIG_KUNIT=y +CONFIG_KVM=y +CONFIG_KVM_GUEST=y +CONFIG_PARAVIRT=y diff --git a/virt/heki/Kconfig b/virt/heki/Kconfig index 0c764e342f48..18895a81a9af 100644 --- a/virt/heki/Kconfig +++ b/virt/heki/Kconfig @@ -28,4 +28,16 @@ config HEKI This feature is helpful in maintaining guest virtual machine security even after the guest kernel has been compromised. +config HEKI_KUNIT_TEST + tristate "KUnit tests for Heki" if !KUNIT_ALL_TESTS + depends on KUNIT + depends on X86 + default KUNIT_ALL_TESTS + help + Build KUnit tests for Landlock. + + See the KUnit documentation in Documentation/dev-tools/kunit + + If you are unsure how to answer this question, answer N. + endif diff --git a/virt/heki/Makefile b/virt/heki/Makefile index 8b10e73a154b..7133545eb5ae 100644 --- a/virt/heki/Makefile +++ b/virt/heki/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_HEKI) += main.o +obj-$(CONFIG_HEKI_KUNIT_TEST) += heki-test.o diff --git a/virt/heki/heki-test.c b/virt/heki/heki-test.c new file mode 100644 index 000000000000..b4e11c21ac5d --- /dev/null +++ b/virt/heki/heki-test.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Hypervisor Enforced Kernel Integrity (Heki) - Tests + * + * Copyright © 2023-2024 Microsoft Corporation + */ + +#include +#include +#include +#include +#include + +/* Returns true on error (i.e. GP fault), false otherwise. */ +static __always_inline bool set_cr4(unsigned long value) +{ + int err = 0; + + might_sleep(); + /* clang-format off */ + asm volatile("1: mov %[value],%%cr4 \n" + "2: \n" + _ASM_EXTABLE_TYPE_REG(1b, 2b, EX_TYPE_ONE_REG, %[err]) + : [value] "+r" (value), [err] "+r" (err) + :); + /* clang-format on */ + return err; +} + +/* Control register pinning tests with SMEP check. */ +static void test_cr_disable_smep(struct kunit *const test) +{ + bool is_vme_set; + + /* SMEP should be initially enabled. */ + KUNIT_ASSERT_TRUE(test, __read_cr4() & X86_CR4_SMEP); + + /* + * Trying to disable SMEP, bypassing kernel self-protection by not + * using cr4_clear_bits(X86_CR4_SMEP), and checking GP fault. + */ + KUNIT_EXPECT_TRUE(test, set_cr4(__read_cr4() & ~X86_CR4_SMEP)); + + /* SMEP should still be enabled. */ + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP); + + /* Re-enabling SMEP doesn't throw a GP fault. */ + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_SMEP)); + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP); + + /* We are allowed to set and unset VME. */ + is_vme_set = __read_cr4() & X86_CR4_VME; + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_VME)); + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_VME); + + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() & ~X86_CR4_VME)); + KUNIT_EXPECT_FALSE(test, __read_cr4() & X86_CR4_VME); + + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_VME)); + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_VME); + + /* SMEP and VME changes should be consistent when setting both. */ + KUNIT_EXPECT_TRUE(test, set_cr4(__read_cr4() & + ~(X86_CR4_SMEP | X86_CR4_VME))); + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP); + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_VME); + + /* Unset VME. */ + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() & ~X86_CR4_VME)); + KUNIT_EXPECT_FALSE(test, __read_cr4() & X86_CR4_VME); + + /* SMEP and VME changes should be consistent when only setting SMEP. */ + KUNIT_EXPECT_TRUE(test, set_cr4(__read_cr4() & + ~(X86_CR4_SMEP | X86_CR4_VME))); + KUNIT_EXPECT_TRUE(test, __read_cr4() & X86_CR4_SMEP); + KUNIT_EXPECT_FALSE(test, __read_cr4() & X86_CR4_VME); + + /* Restores VME. */ + if (is_vme_set) + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() | X86_CR4_VME)); + else + KUNIT_EXPECT_FALSE(test, set_cr4(__read_cr4() & ~X86_CR4_VME)); +} + +/* clang-format off */ +static struct kunit_case test_cases_x86[] = { + KUNIT_CASE(test_cr_disable_smep), + {} +}; +/* clang-format on */ + +static int test_init(struct kunit *test) +{ +#ifdef CONFIG_HEKI + if (heki_enforcing) + return 0; +#else /* CONFIG_HEKI */ + kunit_skip(test, "Heki is not enforced"); +#endif /* CONFIG_HEKI */ + + return 0; +} + +static struct kunit_suite test_suite_x86 = { + .name = "heki_x86", + .init = test_init, + .test_cases = test_cases_x86, +}; + +kunit_test_suite(test_suite_x86); + +MODULE_IMPORT_NS(HEKI_KUNIT_TEST); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Tests for Hypervisor Enforced Kernel Integrity (Heki)"); diff --git a/virt/heki/main.c b/virt/heki/main.c index ef0530a03e09..dcc89befaf66 100644 --- a/virt/heki/main.c +++ b/virt/heki/main.c @@ -11,6 +11,12 @@ #include "common.h" bool heki_enabled __ro_after_init = true; + +#if IS_ENABLED(CONFIG_KUNIT) +bool heki_enforcing = false; +EXPORT_SYMBOL_NS_GPL(heki_enforcing, HEKI_KUNIT_TEST); +#endif /* IS_ENABLED(CONFIG_KUNIT) */ + struct heki heki; /* @@ -47,6 +53,10 @@ void heki_late_init(void) return; pr_warn("Control registers locked\n"); + +#if IS_ENABLED(CONFIG_KUNIT) + heki_enforcing = true; +#endif /* IS_ENABLED(CONFIG_KUNIT) */ } static int __init heki_parse_config(char *str)