[RFC PATCH 03/13] kvm: Add XO memslot type

From: Rick Edgecombe
Date: Thu Oct 03 2019 - 17:39:56 EST


Add XO memslot type to create execute-only guest physical memory based on
the RO memslot. Like the RO memslot, disallow changing the memslot type
to/from XO.

In the EPT case ACC_USER_MASK represents the readable bit, so add the
ability for set_spte() to unset this.

This is based in part on a patch by Yu Zhang.

Signed-off-by: Yu Zhang <yu.c.zhang@xxxxxxxxxxxxxxx>
Signed-off-by: Rick Edgecombe <rick.p.edgecombe@xxxxxxxxx>
---
arch/x86/kvm/mmu.c | 9 ++++++++-
include/uapi/linux/kvm.h | 1 +
tools/include/uapi/linux/kvm.h | 1 +
virt/kvm/kvm_main.c | 15 ++++++++++++++-
4 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/arch/x86/kvm/mmu.c b/arch/x86/kvm/mmu.c
index e44a8053af78..338cc64cc821 100644
--- a/arch/x86/kvm/mmu.c
+++ b/arch/x86/kvm/mmu.c
@@ -2981,6 +2981,8 @@ static int set_spte(struct kvm_vcpu *vcpu, u64 *sptep,

if (pte_access & ACC_USER_MASK)
spte |= shadow_user_mask;
+ else
+ spte &= ~shadow_user_mask;

if (level > PT_PAGE_TABLE_LEVEL)
spte |= PT_PAGE_SIZE_MASK;
@@ -3203,6 +3205,11 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, int write,
int ret;
gfn_t gfn = gpa >> PAGE_SHIFT;
gfn_t base_gfn = gfn;
+ struct kvm_memory_slot *slot = kvm_vcpu_gfn_to_memslot(vcpu, gfn);
+ unsigned int pte_access = ACC_ALL;
+
+ if (slot && slot->flags & KVM_MEM_EXECONLY)
+ pte_access = ACC_EXEC_MASK;

if (!VALID_PAGE(vcpu->arch.mmu->root_hpa))
return RET_PF_RETRY;
@@ -3222,7 +3229,7 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, int write,
}
}

- ret = mmu_set_spte(vcpu, it.sptep, ACC_ALL,
+ ret = mmu_set_spte(vcpu, it.sptep, pte_access,
write, level, base_gfn, pfn, prefault,
map_writable);
direct_pte_prefetch(vcpu, it.sptep);
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 5e3f12d5359e..ede487b7b216 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -109,6 +109,7 @@ struct kvm_userspace_memory_region {
*/
#define KVM_MEM_LOG_DIRTY_PAGES (1UL << 0)
#define KVM_MEM_READONLY (1UL << 1)
+#define KVM_MEM_EXECONLY (1UL << 2)

/* for KVM_IRQ_LINE */
struct kvm_irq_level {
diff --git a/tools/include/uapi/linux/kvm.h b/tools/include/uapi/linux/kvm.h
index 5e3f12d5359e..ede487b7b216 100644
--- a/tools/include/uapi/linux/kvm.h
+++ b/tools/include/uapi/linux/kvm.h
@@ -109,6 +109,7 @@ struct kvm_userspace_memory_region {
*/
#define KVM_MEM_LOG_DIRTY_PAGES (1UL << 0)
#define KVM_MEM_READONLY (1UL << 1)
+#define KVM_MEM_EXECONLY (1UL << 2)

/* for KVM_IRQ_LINE */
struct kvm_irq_level {
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index c6a91b044d8d..65087c1d67be 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -865,6 +865,8 @@ static int check_memory_region_flags(const struct kvm_userspace_memory_region *m
valid_flags |= KVM_MEM_READONLY;
#endif

+ valid_flags |= KVM_MEM_EXECONLY;
+
if (mem->flags & ~valid_flags)
return -EINVAL;

@@ -969,9 +971,12 @@ int __kvm_set_memory_region(struct kvm *kvm,
if (!old.npages)
change = KVM_MR_CREATE;
else { /* Modify an existing slot. */
+ const __u8 changeable = KVM_MEM_READONLY
+ | KVM_MEM_EXECONLY;
+
if ((mem->userspace_addr != old.userspace_addr) ||
(npages != old.npages) ||
- ((new.flags ^ old.flags) & KVM_MEM_READONLY))
+ ((new.flags ^ old.flags) & changeable))
goto out;

if (base_gfn != old.base_gfn)
@@ -1356,6 +1361,11 @@ static bool memslot_is_readonly(struct kvm_memory_slot *slot)
return slot->flags & KVM_MEM_READONLY;
}

+static bool memslot_is_execonly(struct kvm_memory_slot *slot)
+{
+ return slot->flags & KVM_MEM_EXECONLY;
+}
+
static unsigned long __gfn_to_hva_many(struct kvm_memory_slot *slot, gfn_t gfn,
gfn_t *nr_pages, bool write)
{
@@ -1365,6 +1375,9 @@ static unsigned long __gfn_to_hva_many(struct kvm_memory_slot *slot, gfn_t gfn,
if (memslot_is_readonly(slot) && write)
return KVM_HVA_ERR_RO_BAD;

+ if (memslot_is_execonly(slot) && write)
+ return KVM_HVA_ERR_RO_BAD;
+
if (nr_pages)
*nr_pages = slot->npages - (gfn - slot->base_gfn);

--
2.17.1