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 <b...@alien8.de>
Cc: Dave Hansen <dave.han...@linux.intel.com>
Cc: H. Peter Anvin <h...@zytor.com>
Cc: Ingo Molnar <mi...@redhat.com>
Cc: Kees Cook <keesc...@chromium.org>
Cc: Madhavan T. Venkataraman <madve...@linux.microsoft.com>
Cc: Paolo Bonzini <pbonz...@redhat.com>
Cc: Sean Christopherson <sea...@google.com>
Cc: Thomas Gleixner <t...@linutronix.de>
Cc: Vitaly Kuznetsov <vkuzn...@redhat.com>
Cc: Wanpeng Li <wanpen...@tencent.com>
Signed-off-by: Mickaël Salaün <m...@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 f487bf16dd96..b631b1d7ba30 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -5444,6 +5444,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;
@@ -5453,8 +5454,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 4e6c4c21f12c..43c28a6953bf 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)
 
@@ -4644,6 +4648,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;
        }
@@ -6518,6 +6526,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;
@@ -8056,11 +8080,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;
@@ -8080,6 +8153,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. */
@@ -8089,24 +8164,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:
@@ -8114,8 +8202,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;
        }
@@ -8129,9 +8220,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) {
@@ -9998,7 +10090,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 193093112b55..f8f5c32bedd9 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 6864c80ff936..ec32af17add8 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -838,6 +838,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 5b5820d19e71..2477b4a16126 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -279,6 +279,8 @@ struct kvm_xen_exit {
 #define KVM_EXIT_RISCV_CSR        36
 #define KVM_EXIT_NOTIFY           37
 #define KVM_EXIT_MEMORY_FAULT     38
+#define KVM_EXIT_HEKI_CONFIGURE   39
+#define KVM_EXIT_HEKI_DENIAL      40
 
 /* For KVM_EXIT_INTERNAL_ERROR */
 /* Emulate instruction failed. */
@@ -532,6 +534,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];
        };
@@ -1219,6 +1239,8 @@ struct kvm_ppc_resize_hpt {
 #define KVM_CAP_MEMORY_ATTRIBUTES 232
 #define KVM_CAP_GUEST_MEMFD 233
 #define KVM_CAP_VM_TYPES 234
+#define KVM_CAP_HEKI_CONFIGURE 235
+#define KVM_CAP_HEKI_DENIAL 236
 
 #ifdef KVM_CAP_IRQ_ROUTING
 
-- 
2.42.1


Reply via email to