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 <bp@xxxxxxxxx> Cc: Dave Hansen <dave.hansen@xxxxxxxxxxxxxxx> Cc: H. Peter Anvin <hpa@xxxxxxxxx> Cc: Ingo Molnar <mingo@xxxxxxxxxx> Cc: Kees Cook <keescook@xxxxxxxxxxxx> Cc: Madhavan T. Venkataraman <madvenka@xxxxxxxxxxxxxxxxxxx> Cc: Paolo Bonzini <pbonzini@xxxxxxxxxx> Cc: Sean Christopherson <seanjc@xxxxxxxxxx> Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx> Cc: Vitaly Kuznetsov <vkuznets@xxxxxxxxxx> Cc: Wanpeng Li <wanpengli@xxxxxxxxxxx> Signed-off-by: Mickaël Salaün <mic@xxxxxxxxxxx> Link: https://lore.kernel.org/r/20240503131910.307630-4-mic@xxxxxxxxxxx --- 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; -- 2.45.0