[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC PATCH v3 3/5] KVM: x86: Add notifications for Heki policy configuration and violation



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




 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.