From: Ștefan Șicleru <ssicl...@bitdefender.com>

This command is used by the introspection tool to set/clear
the suppress-VE bit for specific guest memory pages.

Signed-off-by: Ștefan Șicleru <ssicl...@bitdefender.com>
Signed-off-by: Adalbert Lazăr <ala...@bitdefender.com>
---
 Documentation/virt/kvm/kvmi.rst               | 42 +++++++++
 arch/x86/include/uapi/asm/kvmi.h              |  8 ++
 arch/x86/kvm/kvmi.c                           |  1 +
 include/uapi/linux/kvmi.h                     |  3 +
 .../testing/selftests/kvm/x86_64/kvmi_test.c  | 91 ++++++++++++++++++-
 virt/kvm/introspection/kvmi.c                 | 29 +++++-
 virt/kvm/introspection/kvmi_int.h             |  1 +
 virt/kvm/introspection/kvmi_msg.c             | 23 +++++
 8 files changed, 196 insertions(+), 2 deletions(-)

diff --git a/Documentation/virt/kvm/kvmi.rst b/Documentation/virt/kvm/kvmi.rst
index c50c40638d46..0f87442f6881 100644
--- a/Documentation/virt/kvm/kvmi.rst
+++ b/Documentation/virt/kvm/kvmi.rst
@@ -1293,6 +1293,48 @@ triggered the EPT violation within a specific EPT view.
 * -KVM_EINVAL - the selected vCPU is invalid
 * -KVM_EAGAIN - the selected vCPU can't be introspected yet
 
+31. KVMI_VM_SET_PAGE_SVE
+------------------------
+
+:Architecture: x86
+:Versions: >= 1
+:Parameters:
+
+::
+
+       struct kvmi_vm_set_page_sve {
+               __u16 view;
+               __u8 suppress;
+               __u8 padding1;
+               __u32 padding2;
+               __u64 gpa;
+       };
+
+:Returns:
+
+::
+
+        struct kvmi_error_code;
+
+Configures the spte 63rd bit (Suppress #VE, SVE) for ``gpa`` on the
+provided EPT ``view``. If ``suppress`` field is 1, the SVE bit will be set.
+If it is 0, the SVE it will be cleared.
+
+If the SVE bit is cleared, EPT violations generated by the provided
+guest physical address will trigger a #VE instead of a #PF, which is
+delivered using gate descriptor 20 in the IDT.
+
+Before configuring the SVE bit, the introspection tool should use
+*KVMI_GET_VERSION* to check if the hardware has support for the #VE
+mechanism (see **KVMI_GET_VERSION**).
+
+:Errors:
+
+* -KVM_EINVAL - padding is not zero
+* -KVM_ENOMEM - not enough memory to add the page tracking structures
+* -KVM_EOPNOTSUPP - an EPT view was selected but the hardware doesn't support 
it
+* -KVM_EINVAL - the selected EPT view is not valid
+
 Events
 ======
 
diff --git a/arch/x86/include/uapi/asm/kvmi.h b/arch/x86/include/uapi/asm/kvmi.h
index d925e6d49f50..17b02624cb4d 100644
--- a/arch/x86/include/uapi/asm/kvmi.h
+++ b/arch/x86/include/uapi/asm/kvmi.h
@@ -182,4 +182,12 @@ struct kvmi_vcpu_set_ve_info {
        __u32 padding3;
 };
 
+struct kvmi_vm_set_page_sve {
+       __u16 view;
+       __u8 suppress;
+       __u8 padding1;
+       __u32 padding2;
+       __u64 gpa;
+};
+
 #endif /* _UAPI_ASM_X86_KVMI_H */
diff --git a/arch/x86/kvm/kvmi.c b/arch/x86/kvm/kvmi.c
index e101ac390809..f3c488f703ec 100644
--- a/arch/x86/kvm/kvmi.c
+++ b/arch/x86/kvm/kvmi.c
@@ -1214,6 +1214,7 @@ static const struct {
        { KVMI_PAGE_ACCESS_R, KVM_PAGE_TRACK_PREREAD },
        { KVMI_PAGE_ACCESS_W, KVM_PAGE_TRACK_PREWRITE },
        { KVMI_PAGE_ACCESS_X, KVM_PAGE_TRACK_PREEXEC },
+       { KVMI_PAGE_SVE,      KVM_PAGE_TRACK_SVE },
 };
 
 void kvmi_arch_update_page_tracking(struct kvm *kvm,
diff --git a/include/uapi/linux/kvmi.h b/include/uapi/linux/kvmi.h
index a17cd1fa16d0..110fb011260b 100644
--- a/include/uapi/linux/kvmi.h
+++ b/include/uapi/linux/kvmi.h
@@ -55,6 +55,8 @@ enum {
        KVMI_VCPU_SET_VE_INFO        = 29,
        KVMI_VCPU_DISABLE_VE         = 30,
 
+       KVMI_VM_SET_PAGE_SVE = 31,
+
        KVMI_NUM_MESSAGES
 };
 
@@ -84,6 +86,7 @@ enum {
        KVMI_PAGE_ACCESS_R = 1 << 0,
        KVMI_PAGE_ACCESS_W = 1 << 1,
        KVMI_PAGE_ACCESS_X = 1 << 2,
+       KVMI_PAGE_SVE      = 1 << 3,
 };
 
 struct kvmi_msg_hdr {
diff --git a/tools/testing/selftests/kvm/x86_64/kvmi_test.c 
b/tools/testing/selftests/kvm/x86_64/kvmi_test.c
index a3ea22f546ec..0dc5b150a739 100644
--- a/tools/testing/selftests/kvm/x86_64/kvmi_test.c
+++ b/tools/testing/selftests/kvm/x86_64/kvmi_test.c
@@ -19,6 +19,7 @@
 
 #include "linux/kvm_para.h"
 #include "linux/kvmi.h"
+#include "asm/kvmi.h"
 
 #define KVM_MAX_EPT_VIEWS 3
 
@@ -39,6 +40,15 @@ static vm_vaddr_t test_ve_info_gva;
 static void *test_ve_info_hva;
 static vm_paddr_t test_ve_info_gpa;
 
+struct vcpu_ve_info {
+       u32 exit_reason;
+       u32 unused;
+       u64 exit_qualification;
+       u64 gva;
+       u64 gpa;
+       u16 eptp_index;
+};
+
 static uint8_t test_write_pattern;
 static int page_size;
 
@@ -53,6 +63,11 @@ struct pf_ev {
        struct kvmi_event_pf pf;
 };
 
+struct exception {
+       uint32_t exception;
+       uint32_t error_code;
+};
+
 struct vcpu_worker_data {
        struct kvm_vm *vm;
        int vcpu_id;
@@ -61,6 +76,8 @@ struct vcpu_worker_data {
        bool shutdown;
        bool restart_on_shutdown;
        bool run_guest_once;
+       bool expect_exception;
+       struct exception ex;
 };
 
 static struct kvmi_features features;
@@ -806,7 +823,9 @@ static void *vcpu_worker(void *data)
 
                TEST_ASSERT(run->exit_reason == KVM_EXIT_IO
                        || (run->exit_reason == KVM_EXIT_SHUTDOWN
-                               && ctx->shutdown),
+                               && ctx->shutdown)
+                       || (run->exit_reason == KVM_EXIT_EXCEPTION
+                               && ctx->expect_exception),
                        "vcpu_run() failed, test_id %d, exit reason %u (%s)\n",
                        ctx->test_id, run->exit_reason,
                        exit_reason_str(run->exit_reason));
@@ -817,6 +836,12 @@ static void *vcpu_worker(void *data)
                        break;
                }
 
+               if (run->exit_reason == KVM_EXIT_EXCEPTION) {
+                       ctx->ex.exception = run->ex.exception;
+                       ctx->ex.error_code = run->ex.error_code;
+                       break;
+               }
+
                TEST_ASSERT(get_ucall(ctx->vm, ctx->vcpu_id, &uc),
                        "No guest request\n");
 
@@ -2288,15 +2313,79 @@ static void disable_ve(struct kvm_vm *vm)
                                sizeof(req), NULL, 0);
 }
 
+static void set_page_sve(__u64 gpa, bool sve)
+{
+       struct {
+               struct kvmi_msg_hdr hdr;
+               struct kvmi_vm_set_page_sve cmd;
+       } req = {};
+
+       req.cmd.gpa = gpa;
+       req.cmd.suppress = sve;
+
+       test_vm_command(KVMI_VM_SET_PAGE_SVE, &req.hdr, sizeof(req),
+                       NULL, 0);
+}
+
 static void test_virtualization_exceptions(struct kvm_vm *vm)
 {
+       struct vcpu_worker_data data = {
+               .vm = vm,
+               .vcpu_id = VCPU_ID,
+               .test_id = GUEST_TEST_PF,
+               .expect_exception = true,
+       };
+       pthread_t vcpu_thread;
+       struct vcpu_ve_info *ve_info;
+
        if (!features.ve) {
                print_skip("#VE not supported");
                return;
        }
 
+       set_page_access(test_gpa, KVMI_PAGE_ACCESS_R);
+       set_page_sve(test_gpa, false);
+
+       new_test_write_pattern(vm);
+
        enable_ve(vm);
+
+       vcpu_thread = start_vcpu_worker(&data);
+
+       wait_vcpu_worker(vcpu_thread);
+
+       TEST_ASSERT(data.ex.exception == VE_VECTOR &&
+                   data.ex.error_code == 0,
+                       "Unexpected exception, vector %u (expected %u), error 
code %u (expected 0)\n",
+                       data.ex.exception, VE_VECTOR, data.ex.error_code);
+
+       ve_info = (struct vcpu_ve_info *)test_ve_info_hva;
+
+       TEST_ASSERT(ve_info->exit_reason == 48 && /* EPT violation */
+                       (ve_info->exit_qualification & 0x18a) &&
+                       ve_info->gva == test_gva &&
+                       ve_info->gpa == test_gpa &&
+                       ve_info->eptp_index == 0,
+                       "#VE exit_reason %u (expected 48), exit qualification 
0x%lx (expected mask 0x18a), gva %lx (expected %lx), gpa %lx (expected %lx), 
ept index %u (expected 0)\n",
+                       ve_info->exit_reason,
+                       ve_info->exit_qualification,
+                       ve_info->gva, test_gva,
+                       ve_info->gpa, test_gpa,
+                       ve_info->eptp_index);
+
+       /* When vcpu_run() is called next, guest will re-execute the
+        * last instruction that triggered a #VE, so the guest
+        * remains in a clean state before executing other tests.
+        * But not before adding write access to test_gpa.
+        */
+       set_page_access(test_gpa, KVMI_PAGE_ACCESS_R | KVMI_PAGE_ACCESS_W);
+
+       /* Disable #VE and and check that a #PF is triggered
+        * instead of a #VE, even though test_gpa is convertible;
+        * here vcpu_run() is called as well.
+        */
        disable_ve(vm);
+       test_event_pf(vm);
 }
 
 static void test_introspection(struct kvm_vm *vm)
diff --git a/virt/kvm/introspection/kvmi.c b/virt/kvm/introspection/kvmi.c
index 6bae2981cda7..665ff223ce84 100644
--- a/virt/kvm/introspection/kvmi.c
+++ b/virt/kvm/introspection/kvmi.c
@@ -28,7 +28,7 @@ static const u8 rwx_access = KVMI_PAGE_ACCESS_R |
                             KVMI_PAGE_ACCESS_X;
 static const u8 full_access = KVMI_PAGE_ACCESS_R |
                             KVMI_PAGE_ACCESS_W |
-                            KVMI_PAGE_ACCESS_X;
+                            KVMI_PAGE_ACCESS_X | KVMI_PAGE_SVE;
 
 void *kvmi_msg_alloc(void)
 {
@@ -1447,3 +1447,30 @@ bool kvmi_tracked_gfn(struct kvm_vcpu *vcpu, gfn_t gfn)
 
        return ret;
 }
+
+int kvmi_cmd_set_page_sve(struct kvm *kvm, gpa_t gpa, u16 view, bool suppress)
+{
+       struct kvmi_mem_access *m;
+       u8 mask = KVMI_PAGE_SVE;
+       bool used = false;
+       int err = 0;
+
+       m = kmem_cache_zalloc(radix_cache, GFP_KERNEL);
+       if (!m)
+               return -KVM_ENOMEM;
+
+       m->gfn = gpa_to_gfn(gpa);
+       m->access = suppress ? KVMI_PAGE_SVE : 0;
+
+       if (radix_tree_preload(GFP_KERNEL))
+               err = -KVM_ENOMEM;
+       else
+               kvmi_set_mem_access(kvm, m, mask, view, &used);
+
+       radix_tree_preload_end();
+
+       if (!used)
+               kmem_cache_free(radix_cache, m);
+
+       return err;
+}
diff --git a/virt/kvm/introspection/kvmi_int.h 
b/virt/kvm/introspection/kvmi_int.h
index a0062fbde49e..915ac75321da 100644
--- a/virt/kvm/introspection/kvmi_int.h
+++ b/virt/kvm/introspection/kvmi_int.h
@@ -88,6 +88,7 @@ int kvmi_cmd_vcpu_set_registers(struct kvm_vcpu *vcpu,
 int kvmi_cmd_set_page_access(struct kvm_introspection *kvmi,
                             const struct kvmi_msg_hdr *msg,
                             const struct kvmi_vm_set_page_access *req);
+int kvmi_cmd_set_page_sve(struct kvm *kvm, gpa_t gpa, u16 view, bool suppress);
 bool kvmi_restricted_page_access(struct kvm_introspection *kvmi, gpa_t gpa,
                                 u8 access, u16 view);
 bool kvmi_pf_event(struct kvm_vcpu *vcpu, gpa_t gpa, gva_t gva, u8 access);
diff --git a/virt/kvm/introspection/kvmi_msg.c 
b/virt/kvm/introspection/kvmi_msg.c
index 664b78d545c3..2b31b117401b 100644
--- a/virt/kvm/introspection/kvmi_msg.c
+++ b/virt/kvm/introspection/kvmi_msg.c
@@ -347,6 +347,28 @@ static int handle_vm_set_page_access(struct 
kvm_introspection *kvmi,
        return kvmi_msg_vm_reply(kvmi, msg, ec, NULL, 0);
 }
 
+static int handle_vm_set_page_sve(struct kvm_introspection *kvmi,
+                                 const struct kvmi_msg_hdr *msg,
+                                 const void *_req)
+{
+       const struct kvmi_vm_set_page_sve *req = _req;
+       int ec;
+
+       if (!is_valid_view(req->view))
+               ec = -KVM_EINVAL;
+       else if (req->suppress > 1)
+               ec = -KVM_EINVAL;
+       else if (req->padding1 || req->padding2)
+               ec = -KVM_EINVAL;
+       else if (req->view != 0 && !kvm_eptp_switching_supported)
+               ec = -KVM_EOPNOTSUPP;
+       else
+               ec = kvmi_cmd_set_page_sve(kvmi->kvm, req->gpa, req->view,
+                                          req->suppress == 1);
+
+       return kvmi_msg_vm_reply(kvmi, msg, ec, NULL, 0);
+}
+
 /*
  * These commands are executed by the receiving thread.
  */
@@ -362,6 +384,7 @@ static int(*const msg_vm[])(struct kvm_introspection *,
        [KVMI_VM_GET_MAX_GFN]     = handle_vm_get_max_gfn,
        [KVMI_VM_READ_PHYSICAL]   = handle_vm_read_physical,
        [KVMI_VM_SET_PAGE_ACCESS] = handle_vm_set_page_access,
+       [KVMI_VM_SET_PAGE_SVE]    = handle_vm_set_page_sve,
        [KVMI_VM_WRITE_PHYSICAL]  = handle_vm_write_physical,
 };
 
_______________________________________________
Virtualization mailing list
Virtualization@lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/virtualization

Reply via email to