This is the 5th version which is 4th version with minor fixes. ROE is a
hypercall that enables host operating system to restrict guest's access to its
own memory. This will provide a hardening mechanism that can be used to stop
rootkits from manipulating kernel static data structures and code. Once a memory
region is protected the guest kernel can't even request undoing the protection.
Memory protected by ROE should be non-swapable because even if the ROE protected
page got swapped out, It won't be possible to write anything in its place.
ROE hypercall should be capable of either protecting a whole memory frame or
parts of it. With these two, it should be possible for guest kernel to protect
its memory and all the page table entries for that memory inside the page table.
I am still not sure whether this should be part of ROE job or the guest's job.
The reason why it would be better to implement this from inside kvm: instead of
(host) user space is the need to access SPTEs to modify the permissions, while
mprotect() from user space can work in theory. It will become a big performance
hit to vmexit and switch to user space mode on each fault, on the other hand,
having the permission handled by EPT should make some remarkable performance
gain.
Our model assumes that an attacker got full root access to a running guest and
his goal is to manipulate kernel code/data (hook syscalls, overwrite IDT ..etc).
There is future work in progress to also put some sort of protection on the page
table register CR3 and other critical registers that can be intercepted by KVM.
This way it won't be possible for an attacker to manipulate any part of the
guests page table.
V4->V5 change log:
- Fixed summary (it was reverted summary)
- Fixed an inaccurate documentation in patch [4/5]
Summary:
Documentation/virtual/kvm/hypercalls.txt | 40 +++++
arch/x86/include/asm/kvm_host.h | 11 +-
arch/x86/kvm/Kconfig | 7 +
arch/x86/kvm/mmu.c | 129 ++++++++++----
arch/x86/kvm/vmx.c | 2 +-
arch/x86/kvm/x86.c | 281 ++++++++++++++++++++++++++++++-
include/linux/kvm_host.h | 29 ++++
include/uapi/linux/kvm_para.h | 5 +
virt/kvm/kvm_main.c | 119 +++++++++++--
9 files changed, 572 insertions(+), 51 deletions(-)
Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
Following up with my previous threads on KVM assisted Anti rootkit
protections.
The current version doesn't address the attacks involving pages
remapping. It is still design in progress, nevertheless, it will be in
my later patch sets.
Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
---
Documentation/virtual/kvm/hypercalls.txt | 31 ++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/Documentation/virtual/kvm/hypercalls.txt b/Documentation/virtual/kvm/hypercalls.txt
index da24c138c8d1..8af64d826f03 100644
--- a/Documentation/virtual/kvm/hypercalls.txt
+++ b/Documentation/virtual/kvm/hypercalls.txt
@@ -141,3 +141,34 @@ a0 corresponds to the APIC ID in the third argument (a2), bit 1
corresponds to the APIC ID a2+1, and so on.
Returns the number of CPUs to which the IPIs were delivered successfully.
+
+7. KVM_HC_ROE
+----------------
+Architecture: x86
+Status: active
+Purpose: Hypercall used to apply Read-Only Enforcement to guest memory and
+registers
+Usage 1:
+ a0: ROE_VERSION
+
+Returns non-signed number that represents the current version of ROE
+implementation current version.
+
+Usage 2:
+
+ a0: ROE_MPROTECT (requires version >= 1)
+ a1: Start address aligned to page boundary.
+ a2: Number of pages to be protected.
+
+This configuration lets a guest kernel have part of its read/write memory
+converted into read-only. This action is irreversible.
+Upon successful run, the number of pages protected is returned.
+
+Error codes:
+ -KVM_ENOSYS: system call being triggered from ring 3 or it is not
+ implemented.
+ -EINVAL: error based on given parameters.
+
+Notes: KVM_HC_ROE can not be triggered from guest Ring 3 (user mode). The
+reason is that user mode malicious software can make use of it to enforce read
+only protection on an arbitrary memory page thus crashing the kernel.
--
2.18.1
This will help sharing data into the slot_level_handler callback. In my
case I need to a share a counter for the pages traversed to use it in some
bitmap. Being able to send arbitrary memory pointer into the
slot_level_handler callback made it easy.
Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
---
arch/x86/kvm/mmu.c | 65 ++++++++++++++++++++++++++--------------------
1 file changed, 37 insertions(+), 28 deletions(-)
diff --git a/arch/x86/kvm/mmu.c b/arch/x86/kvm/mmu.c
index 4cf43ce42959..c4512a47dd16 100644
--- a/arch/x86/kvm/mmu.c
+++ b/arch/x86/kvm/mmu.c
@@ -1492,7 +1492,7 @@ static bool spte_write_protect(u64 *sptep, bool pt_protect)
static bool __rmap_write_protect(struct kvm *kvm,
struct kvm_rmap_head *rmap_head,
- bool pt_protect)
+ bool pt_protect, void *data)
{
u64 *sptep;
struct rmap_iterator iter;
@@ -1531,7 +1531,8 @@ static bool wrprot_ad_disabled_spte(u64 *sptep)
* - W bit on ad-disabled SPTEs.
* Returns true iff any D or W bits were cleared.
*/
-static bool __rmap_clear_dirty(struct kvm *kvm, struct kvm_rmap_head *rmap_head)
+static bool __rmap_clear_dirty(struct kvm *kvm, struct kvm_rmap_head *rmap_head,
+ void *data)
{
u64 *sptep;
struct rmap_iterator iter;
@@ -1557,7 +1558,8 @@ static bool spte_set_dirty(u64 *sptep)
return mmu_spte_update(sptep, spte);
}
-static bool __rmap_set_dirty(struct kvm *kvm, struct kvm_rmap_head *rmap_head)
+static bool __rmap_set_dirty(struct kvm *kvm, struct kvm_rmap_head *rmap_head,
+ void *data)
{
u64 *sptep;
struct rmap_iterator iter;
@@ -1589,7 +1591,7 @@ static void kvm_mmu_write_protect_pt_masked(struct kvm *kvm,
while (mask) {
rmap_head = __gfn_to_rmap(slot->base_gfn + gfn_offset + __ffs(mask),
PT_PAGE_TABLE_LEVEL, slot);
- __rmap_write_protect(kvm, rmap_head, false);
+ __rmap_write_protect(kvm, rmap_head, false, NULL);
/* clear the first set bit */
mask &= mask - 1;
@@ -1615,7 +1617,7 @@ void kvm_mmu_clear_dirty_pt_masked(struct kvm *kvm,
while (mask) {
rmap_head = __gfn_to_rmap(slot->base_gfn + gfn_offset + __ffs(mask),
PT_PAGE_TABLE_LEVEL, slot);
- __rmap_clear_dirty(kvm, rmap_head);
+ __rmap_clear_dirty(kvm, rmap_head, NULL);
/* clear the first set bit */
mask &= mask - 1;
@@ -1668,7 +1670,8 @@ bool kvm_mmu_slot_gfn_write_protect(struct kvm *kvm,
for (i = PT_PAGE_TABLE_LEVEL; i <= PT_MAX_HUGEPAGE_LEVEL; ++i) {
rmap_head = __gfn_to_rmap(gfn, i, slot);
- write_protected |= __rmap_write_protect(kvm, rmap_head, true);
+ write_protected |= __rmap_write_protect(kvm, rmap_head, true,
+ NULL);
}
return write_protected;
@@ -1682,7 +1685,8 @@ static bool rmap_write_protect(struct kvm_vcpu *vcpu, u64 gfn)
return kvm_mmu_slot_gfn_write_protect(vcpu->kvm, slot, gfn);
}
-static bool kvm_zap_rmapp(struct kvm *kvm, struct kvm_rmap_head *rmap_head)
+static bool kvm_zap_rmapp(struct kvm *kvm, struct kvm_rmap_head *rmap_head,
+ void *data)
{
u64 *sptep;
struct rmap_iterator iter;
@@ -1702,7 +1706,7 @@ static int kvm_unmap_rmapp(struct kvm *kvm, struct kvm_rmap_head *rmap_head,
struct kvm_memory_slot *slot, gfn_t gfn, int level,
unsigned long data)
{
- return kvm_zap_rmapp(kvm, rmap_head);
+ return kvm_zap_rmapp(kvm, rmap_head, NULL);
}
static int kvm_set_pte_rmapp(struct kvm *kvm, struct kvm_rmap_head *rmap_head,
@@ -5532,13 +5536,15 @@ void kvm_mmu_uninit_vm(struct kvm *kvm)
}
/* The return value indicates if tlb flush on all vcpus is needed. */
-typedef bool (*slot_level_handler) (struct kvm *kvm, struct kvm_rmap_head *rmap_head);
+typedef bool (*slot_level_handler) (struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head, void *data);
/* The caller should hold mmu-lock before calling this function. */
static __always_inline bool
slot_handle_level_range(struct kvm *kvm, struct kvm_memory_slot *memslot,
slot_level_handler fn, int start_level, int end_level,
- gfn_t start_gfn, gfn_t end_gfn, bool lock_flush_tlb)
+ gfn_t start_gfn, gfn_t end_gfn, bool lock_flush_tlb,
+ void *data)
{
struct slot_rmap_walk_iterator iterator;
bool flush = false;
@@ -5546,7 +5552,7 @@ slot_handle_level_range(struct kvm *kvm, struct kvm_memory_slot *memslot,
for_each_slot_rmap_range(memslot, start_level, end_level, start_gfn,
end_gfn, &iterator) {
if (iterator.rmap)
- flush |= fn(kvm, iterator.rmap);
+ flush |= fn(kvm, iterator.rmap, data);
if (need_resched() || spin_needbreak(&kvm->mmu_lock)) {
if (flush && lock_flush_tlb) {
@@ -5568,36 +5574,36 @@ slot_handle_level_range(struct kvm *kvm, struct kvm_memory_slot *memslot,
static __always_inline bool
slot_handle_level(struct kvm *kvm, struct kvm_memory_slot *memslot,
slot_level_handler fn, int start_level, int end_level,
- bool lock_flush_tlb)
+ bool lock_flush_tlb, void *data)
{
return slot_handle_level_range(kvm, memslot, fn, start_level,
end_level, memslot->base_gfn,
memslot->base_gfn + memslot->npages - 1,
- lock_flush_tlb);
+ lock_flush_tlb, data);
}
static __always_inline bool
slot_handle_all_level(struct kvm *kvm, struct kvm_memory_slot *memslot,
- slot_level_handler fn, bool lock_flush_tlb)
+ slot_level_handler fn, bool lock_flush_tlb, void *data)
{
return slot_handle_level(kvm, memslot, fn, PT_PAGE_TABLE_LEVEL,
- PT_MAX_HUGEPAGE_LEVEL, lock_flush_tlb);
+ PT_MAX_HUGEPAGE_LEVEL, lock_flush_tlb, data);
}
static __always_inline bool
slot_handle_large_level(struct kvm *kvm, struct kvm_memory_slot *memslot,
- slot_level_handler fn, bool lock_flush_tlb)
+ slot_level_handler fn, bool lock_flush_tlb, void *data)
{
return slot_handle_level(kvm, memslot, fn, PT_PAGE_TABLE_LEVEL + 1,
- PT_MAX_HUGEPAGE_LEVEL, lock_flush_tlb);
+ PT_MAX_HUGEPAGE_LEVEL, lock_flush_tlb, data);
}
static __always_inline bool
slot_handle_leaf(struct kvm *kvm, struct kvm_memory_slot *memslot,
- slot_level_handler fn, bool lock_flush_tlb)
+ slot_level_handler fn, bool lock_flush_tlb, void *data)
{
return slot_handle_level(kvm, memslot, fn, PT_PAGE_TABLE_LEVEL,
- PT_PAGE_TABLE_LEVEL, lock_flush_tlb);
+ PT_PAGE_TABLE_LEVEL, lock_flush_tlb, data);
}
void kvm_zap_gfn_range(struct kvm *kvm, gfn_t gfn_start, gfn_t gfn_end)
@@ -5619,7 +5625,7 @@ void kvm_zap_gfn_range(struct kvm *kvm, gfn_t gfn_start, gfn_t gfn_end)
slot_handle_level_range(kvm, memslot, kvm_zap_rmapp,
PT_PAGE_TABLE_LEVEL, PT_MAX_HUGEPAGE_LEVEL,
- start, end - 1, true);
+ start, end - 1, true, NULL);
}
}
@@ -5627,9 +5633,10 @@ void kvm_zap_gfn_range(struct kvm *kvm, gfn_t gfn_start, gfn_t gfn_end)
}
static bool slot_rmap_write_protect(struct kvm *kvm,
- struct kvm_rmap_head *rmap_head)
+ struct kvm_rmap_head *rmap_head,
+ void *data)
{
- return __rmap_write_protect(kvm, rmap_head, false);
+ return __rmap_write_protect(kvm, rmap_head, false, data);
}
void kvm_mmu_slot_remove_write_access(struct kvm *kvm,
@@ -5639,7 +5646,7 @@ void kvm_mmu_slot_remove_write_access(struct kvm *kvm,
spin_lock(&kvm->mmu_lock);
flush = slot_handle_all_level(kvm, memslot, slot_rmap_write_protect,
- false);
+ false, NULL);
spin_unlock(&kvm->mmu_lock);
/*
@@ -5665,7 +5672,8 @@ void kvm_mmu_slot_remove_write_access(struct kvm *kvm,
}
static bool kvm_mmu_zap_collapsible_spte(struct kvm *kvm,
- struct kvm_rmap_head *rmap_head)
+ struct kvm_rmap_head *rmap_head,
+ void *data)
{
u64 *sptep;
struct rmap_iterator iter;
@@ -5703,7 +5711,7 @@ void kvm_mmu_zap_collapsible_sptes(struct kvm *kvm,
/* FIXME: const-ify all uses of struct kvm_memory_slot. */
spin_lock(&kvm->mmu_lock);
slot_handle_leaf(kvm, (struct kvm_memory_slot *)memslot,
- kvm_mmu_zap_collapsible_spte, true);
+ kvm_mmu_zap_collapsible_spte, true, NULL);
spin_unlock(&kvm->mmu_lock);
}
@@ -5713,7 +5721,7 @@ void kvm_mmu_slot_leaf_clear_dirty(struct kvm *kvm,
bool flush;
spin_lock(&kvm->mmu_lock);
- flush = slot_handle_leaf(kvm, memslot, __rmap_clear_dirty, false);
+ flush = slot_handle_leaf(kvm, memslot, __rmap_clear_dirty, false, NULL);
spin_unlock(&kvm->mmu_lock);
lockdep_assert_held(&kvm->slots_lock);
@@ -5736,7 +5744,7 @@ void kvm_mmu_slot_largepage_remove_write_access(struct kvm *kvm,
spin_lock(&kvm->mmu_lock);
flush = slot_handle_large_level(kvm, memslot, slot_rmap_write_protect,
- false);
+ false, NULL);
spin_unlock(&kvm->mmu_lock);
/* see kvm_mmu_slot_remove_write_access */
@@ -5753,7 +5761,8 @@ void kvm_mmu_slot_set_dirty(struct kvm *kvm,
bool flush;
spin_lock(&kvm->mmu_lock);
- flush = slot_handle_all_level(kvm, memslot, __rmap_set_dirty, false);
+ flush = slot_handle_all_level(kvm, memslot, __rmap_set_dirty, false,
+ NULL);
spin_unlock(&kvm->mmu_lock);
lockdep_assert_held(&kvm->slots_lock);
--
2.18.1
This should be a little bit more readable and prone to memory leaks
Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
---
virt/kvm/kvm_main.c | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 2d3011e8490e..79c98db03c84 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -550,11 +550,11 @@ static void kvm_destroy_dirty_bitmap(struct kvm_memory_slot *memslot)
* Free any memory in @free but not in @dont.
*/
static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
- struct kvm_memory_slot *dont)
+ struct kvm_memory_slot *dont,
+ enum kvm_mr_change change)
{
+ if (change == KVM_MR_DELETE) {
#ifdef CONFIG_KVM_ROE
- if (!dont) {
- //TODO still this might leak
struct protected_chunk *pos, *n;
struct list_head *head = free->prot_list;
kvfree(free->roe_bitmap);
@@ -564,10 +564,9 @@ static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
kvfree(pos);
}
kvfree(free->prot_list);
- }
#endif
- if (!dont || free->dirty_bitmap != dont->dirty_bitmap)
kvm_destroy_dirty_bitmap(free);
+ }
kvm_arch_free_memslot(kvm, free, dont);
@@ -582,7 +581,7 @@ static void kvm_free_memslots(struct kvm *kvm, struct kvm_memslots *slots)
return;
kvm_for_each_memslot(memslot, slots)
- kvm_free_memslot(kvm, memslot, NULL);
+ kvm_free_memslot(kvm, memslot, NULL, KVM_MR_DELETE);
kvfree(slots);
}
@@ -1100,14 +1099,14 @@ int __kvm_set_memory_region(struct kvm *kvm,
kvm_arch_commit_memory_region(kvm, mem, &old, &new, change);
- kvm_free_memslot(kvm, &old, &new);
+ kvm_free_memslot(kvm, &old, &new, change);
kvfree(old_memslots);
return 0;
out_slots:
kvfree(slots);
out_free:
- kvm_free_memslot(kvm, &new, &old);
+ kvm_free_memslot(kvm, &new, &old, change);
out:
return r;
}
--
2.18.1
This patch introduces a hypercall implemented for X86 that can assist
against subset of kernel rootkits, it works by place readonly protection in
shadow PTE. The end result protection is also kept in a bitmap for each
kvm_memory_slot and is used as reference when updating SPTEs. The whole
goal is to protect the guest kernel static data from modification if
attacker is running from guest ring 0, for this reason there is no
hypercall to revert effect of Memory ROE hypercall. This patch doesn't
implement integrity check on guest TLB so obvious attack on the current
implementation will involve guest virtual address -> guest physical
address remapping, but there are plans to fix that.
Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
---
arch/x86/include/asm/kvm_host.h | 11 ++-
arch/x86/kvm/Kconfig | 7 ++
arch/x86/kvm/mmu.c | 72 +++++++++++++---
arch/x86/kvm/x86.c | 143 +++++++++++++++++++++++++++++++-
include/linux/kvm_host.h | 3 +
include/uapi/linux/kvm_para.h | 4 +
virt/kvm/kvm_main.c | 34 +++++++-
7 files changed, 255 insertions(+), 19 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 55e51ff7e421..40a52232fb4f 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -247,6 +247,15 @@ struct kvm_mmu_memory_cache {
void *objects[KVM_NR_MEM_OBJS];
};
+/*
+ * This is internal structure used to be be able to access kvm memory slot and
+ * have track of the number of current PTE when doing shadow PTE walk
+ */
+struct kvm_write_access_data {
+ int i;
+ struct kvm_memory_slot *memslot;
+};
+
/*
* the pages used as guest page table on soft mmu are tracked by
* kvm_memory_slot.arch.gfn_track which is 16 bits, so the role bits used
@@ -1229,7 +1238,7 @@ void kvm_mmu_set_mask_ptes(u64 user_mask, u64 accessed_mask,
u64 acc_track_mask, u64 me_mask);
void kvm_mmu_reset_context(struct kvm_vcpu *vcpu);
-void kvm_mmu_slot_remove_write_access(struct kvm *kvm,
+void kvm_mmu_slot_apply_write_access(struct kvm *kvm,
struct kvm_memory_slot *memslot);
void kvm_mmu_zap_collapsible_sptes(struct kvm *kvm,
const struct kvm_memory_slot *memslot);
diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig
index 1bbec387d289..2fcbb1788a24 100644
--- a/arch/x86/kvm/Kconfig
+++ b/arch/x86/kvm/Kconfig
@@ -96,6 +96,13 @@ config KVM_MMU_AUDIT
This option adds a R/W kVM module parameter 'mmu_audit', which allows
auditing of KVM MMU events at runtime.
+config KVM_ROE
+ bool "Hypercall Memory Read-Only Enforcement"
+ depends on KVM && X86
+ help
+ This option adds KVM_HC_ROE hypercall to kvm as a hardening
+ mechanism to protect memory pages from being edited.
+
# OK, it's a little counter-intuitive to do this, but it puts it neatly under
# the virtualization menu.
source drivers/vhost/Kconfig
diff --git a/arch/x86/kvm/mmu.c b/arch/x86/kvm/mmu.c
index c4512a47dd16..7d9b63ddbb81 100644
--- a/arch/x86/kvm/mmu.c
+++ b/arch/x86/kvm/mmu.c
@@ -1490,9 +1490,8 @@ static bool spte_write_protect(u64 *sptep, bool pt_protect)
return mmu_spte_update(sptep, spte);
}
-static bool __rmap_write_protect(struct kvm *kvm,
- struct kvm_rmap_head *rmap_head,
- bool pt_protect, void *data)
+static bool __rmap_write_protection(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head, bool pt_protect)
{
u64 *sptep;
struct rmap_iterator iter;
@@ -1504,6 +1503,38 @@ static bool __rmap_write_protect(struct kvm *kvm,
return flush;
}
+#ifdef CONFIG_KVM_ROE
+static bool __rmap_write_protect_roe(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head,
+ bool pt_protect,
+ struct kvm_write_access_data *d)
+{
+ u64 *sptep;
+ struct rmap_iterator iter;
+ bool prot;
+ bool flush = false;
+
+ for_each_rmap_spte(rmap_head, &iter, sptep) {
+ prot = !test_bit(d->i, d->memslot->roe_bitmap) && pt_protect;
+ flush |= spte_write_protect(sptep, prot);
+ d->i++;
+ }
+ return flush;
+}
+#endif
+
+static bool __rmap_write_protect(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head,
+ bool pt_protect,
+ struct kvm_write_access_data *d)
+{
+#ifdef CONFIG_KVM_ROE
+ if (d != NULL)
+ return __rmap_write_protect_roe(kvm, rmap_head, pt_protect, d);
+#endif
+ return __rmap_write_protection(kvm, rmap_head, pt_protect);
+}
+
static bool spte_clear_dirty(u64 *sptep)
{
u64 spte = *sptep;
@@ -1591,7 +1622,7 @@ static void kvm_mmu_write_protect_pt_masked(struct kvm *kvm,
while (mask) {
rmap_head = __gfn_to_rmap(slot->base_gfn + gfn_offset + __ffs(mask),
PT_PAGE_TABLE_LEVEL, slot);
- __rmap_write_protect(kvm, rmap_head, false, NULL);
+ __rmap_write_protection(kvm, rmap_head, false);
/* clear the first set bit */
mask &= mask - 1;
@@ -1667,11 +1698,15 @@ bool kvm_mmu_slot_gfn_write_protect(struct kvm *kvm,
struct kvm_rmap_head *rmap_head;
int i;
bool write_protected = false;
+ struct kvm_write_access_data data = {
+ .i = 0,
+ .memslot = slot,
+ };
for (i = PT_PAGE_TABLE_LEVEL; i <= PT_MAX_HUGEPAGE_LEVEL; ++i) {
rmap_head = __gfn_to_rmap(gfn, i, slot);
write_protected |= __rmap_write_protect(kvm, rmap_head, true,
- NULL);
+ &data);
}
return write_protected;
@@ -5636,21 +5671,36 @@ static bool slot_rmap_write_protect(struct kvm *kvm,
struct kvm_rmap_head *rmap_head,
void *data)
{
- return __rmap_write_protect(kvm, rmap_head, false, data);
+ return __rmap_write_protect(kvm, rmap_head, false,
+ (struct kvm_write_access_data *)data);
}
-void kvm_mmu_slot_remove_write_access(struct kvm *kvm,
+static bool slot_rmap_apply_protection(struct kvm *kvm,
+ struct kvm_rmap_head *rmap_head,
+ void *data)
+{
+ struct kvm_write_access_data *d = (struct kvm_write_access_data *) data;
+ bool prot_mask = !(d->memslot->flags & KVM_MEM_READONLY);
+
+ return __rmap_write_protect(kvm, rmap_head, prot_mask, d);
+}
+
+void kvm_mmu_slot_apply_write_access(struct kvm *kvm,
struct kvm_memory_slot *memslot)
{
bool flush;
+ struct kvm_write_access_data data = {
+ .i = 0,
+ .memslot = memslot,
+ };
spin_lock(&kvm->mmu_lock);
- flush = slot_handle_all_level(kvm, memslot, slot_rmap_write_protect,
- false, NULL);
+ flush = slot_handle_all_level(kvm, memslot, slot_rmap_apply_protection,
+ false, &data);
spin_unlock(&kvm->mmu_lock);
/*
- * kvm_mmu_slot_remove_write_access() and kvm_vm_ioctl_get_dirty_log()
+ * kvm_mmu_slot_apply_write_access() and kvm_vm_ioctl_get_dirty_log()
* which do tlb flush out of mmu-lock should be serialized by
* kvm->slots_lock otherwise tlb flush would be missed.
*/
@@ -5747,7 +5797,7 @@ void kvm_mmu_slot_largepage_remove_write_access(struct kvm *kvm,
false, NULL);
spin_unlock(&kvm->mmu_lock);
- /* see kvm_mmu_slot_remove_write_access */
+ /* see kvm_mmu_slot_apply_write_access*/
lockdep_assert_held(&kvm->slots_lock);
if (flush)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 66d66d77caee..ce798b30b69a 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -4409,7 +4409,7 @@ int kvm_vm_ioctl_get_dirty_log(struct kvm *kvm, struct kvm_dirty_log *log)
/*
* All the TLBs can be flushed out of mmu lock, see the comments in
- * kvm_mmu_slot_remove_write_access().
+ * kvm_mmu_slot_apply_write_access().
*/
lockdep_assert_held(&kvm->slots_lock);
if (is_dirty)
@@ -6928,7 +6928,137 @@ static int kvm_pv_clock_pairing(struct kvm_vcpu *vcpu, gpa_t paddr,
}
#endif
-/*
+#ifdef CONFIG_KVM_ROE
+static void kvm_roe_protect_slot(struct kvm *kvm, struct kvm_memory_slot *slot,
+ gfn_t gfn, u64 npages)
+{
+ int i;
+
+ for (i = gfn - slot->base_gfn; i < gfn + npages - slot->base_gfn; i++)
+ set_bit(i, slot->roe_bitmap);
+ kvm_mmu_slot_apply_write_access(kvm, slot);
+ kvm_arch_flush_shadow_memslot(kvm, slot);
+}
+
+static int __kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages)
+{
+ struct kvm_memory_slot *slot;
+ gfn_t gfn = gpa >> PAGE_SHIFT;
+ int count = 0;
+
+ while (npages != 0) {
+ slot = gfn_to_memslot(kvm, gfn);
+ if (!slot) {
+ gfn += 1;
+ npages -= 1;
+ continue;
+ }
+ if (gfn + npages > slot->base_gfn + slot->npages) {
+ u64 _npages = slot->base_gfn + slot->npages - gfn;
+
+ kvm_roe_protect_slot(kvm, slot, gfn, _npages);
+ gfn += _npages;
+ count += _npages;
+ npages -= _npages;
+ } else {
+ kvm_roe_protect_slot(kvm, slot, gfn, npages);
+ count += npages;
+ npages = 0;
+ }
+ }
+ if (count == 0)
+ return -EINVAL;
+ return count;
+}
+
+static int kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages)
+{
+ int r;
+
+ mutex_lock(&kvm->slots_lock);
+ r = __kvm_roe_protect_range(kvm, gpa, npages);
+ mutex_unlock(&kvm->slots_lock);
+ return r;
+}
+
+static bool kvm_roe_userspace(struct kvm_vcpu *vcpu)
+{
+ u64 rflags;
+ u64 cr0 = kvm_read_cr0(vcpu);
+ u64 iopl;
+
+ // first checking we are not in protected mode
+ if ((cr0 & 1) == 0)
+ return false;
+ /*
+ * we don't need to worry about comments in __get_regs
+ * because we are sure that this function will only be
+ * triggered at the end of a hypercall
+ */
+ rflags = kvm_get_rflags(vcpu);
+ iopl = (rflags >> 12) & 3;
+ if (iopl != 3)
+ return false;
+ return true;
+}
+
+static int kvm_roe_full_protect_range(struct kvm_vcpu *vcpu, u64 gva,
+ u64 npages)
+{
+ struct kvm *kvm = vcpu->kvm;
+ gpa_t gpa;
+ u64 hva;
+ u64 count = 0;
+ int i;
+ int status;
+
+ if (gva & ~PAGE_MASK)
+ return -EINVAL;
+ // We need to make sure that there will be no overflow
+ if ((npages << PAGE_SHIFT) >> PAGE_SHIFT != npages || npages == 0)
+ return -EINVAL;
+ for (i = 0; i < npages; i++) {
+ gpa = kvm_mmu_gva_to_gpa_system(vcpu, gva + (i << PAGE_SHIFT),
+ NULL);
+ hva = gfn_to_hva(kvm, gpa >> PAGE_SHIFT);
+ if (kvm_is_error_hva(hva))
+ continue;
+ if (!access_ok(VERIFY_WRITE, hva, 1 << PAGE_SHIFT))
+ continue;
+ status = kvm_roe_protect_range(vcpu->kvm, gpa, 1);
+ if (status > 0)
+ count += status;
+ }
+ if (count == 0)
+ return -EINVAL;
+ return count;
+}
+
+static int kvm_roe(struct kvm_vcpu *vcpu, u64 a0, u64 a1, u64 a2, u64 a3)
+{
+ int ret;
+ /*
+ * First we need to make sure that we are running from something that
+ * isn't usermode
+ */
+ if (kvm_roe_userspace(vcpu))
+ return -KVM_ENOSYS;
+ switch (a0) {
+ case ROE_VERSION:
+ ret = 1; //current version
+ break;
+ case ROE_MPROTECT:
+ ret = kvm_roe_full_protect_range(vcpu, a1, a2);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+#endif
+
+ /*
* kvm_pv_kick_cpu_op: Kick a vcpu.
*
* @apicid - apicid of vcpu to be kicked.
@@ -6998,6 +7128,11 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
case KVM_HC_SEND_IPI:
ret = kvm_pv_send_ipi(vcpu->kvm, a0, a1, a2, a3, op_64_bit);
break;
+#endif
+#ifdef CONFIG_KVM_ROE
+ case KVM_HC_ROE:
+ ret = kvm_roe(vcpu, a0, a1, a2, a3);
+ break;
#endif
default:
ret = -KVM_ENOSYS;
@@ -9261,8 +9396,8 @@ static void kvm_mmu_slot_apply_flags(struct kvm *kvm,
struct kvm_memory_slot *new)
{
/* Still write protect RO slot */
+ kvm_mmu_slot_apply_write_access(kvm, new);
if (new->flags & KVM_MEM_READONLY) {
- kvm_mmu_slot_remove_write_access(kvm, new);
return;
}
@@ -9300,7 +9435,7 @@ static void kvm_mmu_slot_apply_flags(struct kvm *kvm,
if (kvm_x86_ops->slot_enable_log_dirty)
kvm_x86_ops->slot_enable_log_dirty(kvm, new);
else
- kvm_mmu_slot_remove_write_access(kvm, new);
+ kvm_mmu_slot_apply_write_access(kvm, new);
} else {
if (kvm_x86_ops->slot_disable_log_dirty)
kvm_x86_ops->slot_disable_log_dirty(kvm, new);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index c926698040e0..be6885bc28bc 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -297,6 +297,9 @@ static inline int kvm_vcpu_exiting_guest_mode(struct kvm_vcpu *vcpu)
struct kvm_memory_slot {
gfn_t base_gfn;
unsigned long npages;
+#ifdef CONFIG_KVM_ROE
+ unsigned long *roe_bitmap;
+#endif
unsigned long *dirty_bitmap;
struct kvm_arch_memory_slot arch;
unsigned long userspace_addr;
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 6c0ce49931e5..e6004e0750fd 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -28,7 +28,11 @@
#define KVM_HC_MIPS_CONSOLE_OUTPUT 8
#define KVM_HC_CLOCK_PAIRING 9
#define KVM_HC_SEND_IPI 10
+#define KVM_HC_ROE 11
+/* ROE Functionality parameters */
+#define ROE_VERSION 0
+#define ROE_MPROTECT 1
/*
* hypercalls use architecture specific
*/
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 786ade1843a2..f9382a839361 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -552,6 +552,11 @@ static void kvm_destroy_dirty_bitmap(struct kvm_memory_slot *memslot)
static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
struct kvm_memory_slot *dont)
{
+#ifdef CONFIG_KVM_ROE
+ if (!dont)
+ kvfree(free->roe_bitmap);
+#endif
+
if (!dont || free->dirty_bitmap != dont->dirty_bitmap)
kvm_destroy_dirty_bitmap(free);
@@ -798,6 +803,17 @@ static int kvm_create_dirty_bitmap(struct kvm_memory_slot *memslot)
return 0;
}
+static int kvm_init_roe_bitmap(struct kvm_memory_slot *slot)
+{
+#ifdef CONFIG_KVM_ROE
+ slot->roe_bitmap = kvzalloc(BITS_TO_LONGS(slot->npages) *
+ sizeof(unsigned long), GFP_KERNEL);
+ if (!slot->roe_bitmap)
+ return -ENOMEM;
+#endif
+ return 0;
+}
+
/*
* Insert memslot and re-sort memslots based on their GFN,
* so binary search could be used to lookup GFN.
@@ -1020,6 +1036,8 @@ int __kvm_set_memory_region(struct kvm *kvm,
if (kvm_create_dirty_bitmap(&new) < 0)
goto out_free;
}
+ if (kvm_init_roe_bitmap(&new) < 0)
+ goto out_free;
slots = kvzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
if (!slots)
@@ -1273,13 +1291,23 @@ static bool memslot_is_readonly(struct kvm_memory_slot *slot)
return slot->flags & KVM_MEM_READONLY;
}
+static bool gfn_is_readonly(struct kvm_memory_slot *slot, gfn_t gfn)
+{
+#ifdef CONFIG_KVM_ROE
+ return test_bit(gfn - slot->base_gfn, slot->roe_bitmap) ||
+ memslot_is_readonly(slot);
+#else
+ return memslot_is_readonly(slot);
+#endif
+}
+
static unsigned long __gfn_to_hva_many(struct kvm_memory_slot *slot, gfn_t gfn,
gfn_t *nr_pages, bool write)
{
if (!slot || slot->flags & KVM_MEMSLOT_INVALID)
return KVM_HVA_ERR_BAD;
- if (memslot_is_readonly(slot) && write)
+ if (gfn_is_readonly(slot, gfn) && write)
return KVM_HVA_ERR_RO_BAD;
if (nr_pages)
@@ -1327,7 +1355,7 @@ unsigned long gfn_to_hva_memslot_prot(struct kvm_memory_slot *slot,
unsigned long hva = __gfn_to_hva_many(slot, gfn, NULL, false);
if (!kvm_is_error_hva(hva) && writable)
- *writable = !memslot_is_readonly(slot);
+ *writable = !gfn_is_readonly(slot, gfn);
return hva;
}
@@ -1565,7 +1593,7 @@ kvm_pfn_t __gfn_to_pfn_memslot(struct kvm_memory_slot *slot, gfn_t gfn,
}
/* Do not map writable pfn in the readonly memslot. */
- if (writable && memslot_is_readonly(slot)) {
+ if (writable && gfn_is_readonly(slot, gfn)) {
*writable = false;
writable = NULL;
}
--
2.18.1
This patch documents and implements ROE_MPROTECT_CHUNK, a part of ROE
hypercall designed to protect regions of a memory page with byte
granularity. This feature provides a key primitive to protect against
attacks involving pages remapping. However this attack will be
addressed in future patches.
Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
---
Documentation/virtual/kvm/hypercalls.txt | 9 ++
arch/x86/kvm/mmu.c | 6 +-
arch/x86/kvm/x86.c | 156 +++++++++++++++++++++--
include/linux/kvm_host.h | 26 ++++
include/uapi/linux/kvm_para.h | 1 +
virt/kvm/kvm_main.c | 88 +++++++++++--
6 files changed, 266 insertions(+), 20 deletions(-)
diff --git a/Documentation/virtual/kvm/hypercalls.txt b/Documentation/virtual/kvm/hypercalls.txt
index 8af64d826f03..a31f316ce6e6 100644
--- a/Documentation/virtual/kvm/hypercalls.txt
+++ b/Documentation/virtual/kvm/hypercalls.txt
@@ -164,6 +164,15 @@ This configuration lets a guest kernel have part of its read/write memory
converted into read-only. This action is irreversible.
Upon successful run, the number of pages protected is returned.
+Usage 3:
+ a0: ROE_MPROTECT_CHUNK (requires version >= 2)
+ a1: Start address aligned to page boundary.
+ a2: Number of bytes to be protected.
+This configuration lets a guest kernel have part of its read/write memory
+converted into read-only with bytes granularity. ROE_MPROTECT_CHUNK is
+relatively slow compared to ROE_MPROTECT. This action is irreversible.
+Upon successful run, the number of bytes protected is returned.
+
Error codes:
-KVM_ENOSYS: system call being triggered from ring 3 or it is not
implemented.
diff --git a/arch/x86/kvm/mmu.c b/arch/x86/kvm/mmu.c
index 7d9b63ddbb81..becb95b5f76e 100644
--- a/arch/x86/kvm/mmu.c
+++ b/arch/x86/kvm/mmu.c
@@ -1513,9 +1513,11 @@ static bool __rmap_write_protect_roe(struct kvm *kvm,
struct rmap_iterator iter;
bool prot;
bool flush = false;
-
+ void *full_bmp = d->memslot->roe_bitmap;
+ void *part_bmp = d->memslot->partial_roe_bitmap;
for_each_rmap_spte(rmap_head, &iter, sptep) {
- prot = !test_bit(d->i, d->memslot->roe_bitmap) && pt_protect;
+ prot = !(test_bit(d->i, full_bmp) || test_bit(d->i, part_bmp));
+ prot = prot && pt_protect;
flush |= spte_write_protect(sptep, prot);
d->i++;
}
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index ce798b30b69a..581bd18910df 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -6930,17 +6930,23 @@ static int kvm_pv_clock_pairing(struct kvm_vcpu *vcpu, gpa_t paddr,
#ifdef CONFIG_KVM_ROE
static void kvm_roe_protect_slot(struct kvm *kvm, struct kvm_memory_slot *slot,
- gfn_t gfn, u64 npages)
+ gfn_t gfn, u64 npages, bool partial)
{
int i;
+ void *bitmap;
+ if (partial)
+ bitmap = slot->partial_roe_bitmap;
+ else
+ bitmap = slot->roe_bitmap;
for (i = gfn - slot->base_gfn; i < gfn + npages - slot->base_gfn; i++)
- set_bit(i, slot->roe_bitmap);
+ set_bit(i, bitmap);
kvm_mmu_slot_apply_write_access(kvm, slot);
kvm_arch_flush_shadow_memslot(kvm, slot);
}
-static int __kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages)
+static int __kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages,
+ bool partial)
{
struct kvm_memory_slot *slot;
gfn_t gfn = gpa >> PAGE_SHIFT;
@@ -6956,12 +6962,12 @@ static int __kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages)
if (gfn + npages > slot->base_gfn + slot->npages) {
u64 _npages = slot->base_gfn + slot->npages - gfn;
- kvm_roe_protect_slot(kvm, slot, gfn, _npages);
+ kvm_roe_protect_slot(kvm, slot, gfn, _npages, partial);
gfn += _npages;
count += _npages;
npages -= _npages;
} else {
- kvm_roe_protect_slot(kvm, slot, gfn, npages);
+ kvm_roe_protect_slot(kvm, slot, gfn, npages, partial);
count += npages;
npages = 0;
}
@@ -6971,12 +6977,13 @@ static int __kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages)
return count;
}
-static int kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages)
+static int kvm_roe_protect_range(struct kvm *kvm, gpa_t gpa, u64 npages,
+ bool partial)
{
int r;
mutex_lock(&kvm->slots_lock);
- r = __kvm_roe_protect_range(kvm, gpa, npages);
+ r = __kvm_roe_protect_range(kvm, gpa, npages, partial);
mutex_unlock(&kvm->slots_lock);
return r;
}
@@ -7025,7 +7032,7 @@ static int kvm_roe_full_protect_range(struct kvm_vcpu *vcpu, u64 gva,
continue;
if (!access_ok(VERIFY_WRITE, hva, 1 << PAGE_SHIFT))
continue;
- status = kvm_roe_protect_range(vcpu->kvm, gpa, 1);
+ status = kvm_roe_protect_range(vcpu->kvm, gpa, 1, false);
if (status > 0)
count += status;
}
@@ -7033,7 +7040,135 @@ static int kvm_roe_full_protect_range(struct kvm_vcpu *vcpu, u64 gva,
return -EINVAL;
return count;
}
+static int kvm_roe_insert_chunk_next(struct list_head *pos, u64 gpa, u64 size)
+{
+ struct protected_chunk *chunk;
+
+ chunk = kvzalloc(sizeof(struct protected_chunk), GFP_KERNEL);
+ chunk->gpa = gpa;
+ chunk->size = size;
+ INIT_LIST_HEAD(&chunk->list);
+ list_add(&chunk->list, pos);
+ return size;
+}
+static int kvm_roe_expand_chunk(struct protected_chunk *pos, u64 gpa, u64 size)
+{
+ u64 old_ptr = pos->gpa;
+ u64 old_size = pos->size;
+
+ if (gpa < old_ptr)
+ pos->gpa = gpa;
+ if (gpa + size > old_ptr + old_size)
+ pos->size = gpa + size - pos->gpa;
+ return size;
+}
+
+static bool kvm_roe_merge_chunks(struct protected_chunk *chunk)
+{
+ /*attempt merging 2 consecutive given the first one*/
+ struct protected_chunk *next = list_next_entry(chunk, list);
+
+ if (!kvm_roe_range_overlap(chunk, next->gpa, next->size))
+ return false;
+ kvm_roe_expand_chunk(chunk, next->gpa, next->size);
+ list_del(&next->list);
+ kvfree(next);
+ return true;
+}
+static int __kvm_roe_insert_chunk(struct kvm_memory_slot *slot, u64 gpa,
+ u64 size)
+{
+ /* kvm->slots_lock must be acquired*/
+ struct protected_chunk *pos;
+ struct list_head *head = slot->prot_list;
+
+ if (list_empty(head))
+ return kvm_roe_insert_chunk_next(head, gpa, size);
+ /*
+ * pos here will never get deleted maybe the next one will
+ * that is why list_for_each_entry_safe is completely unsafe
+ */
+ list_for_each_entry(pos, head, list) {
+ if (kvm_roe_range_overlap(pos, gpa, size)) {
+ int ret = kvm_roe_expand_chunk(pos, gpa, size);
+
+ while (head != pos->list.next)
+ if (!kvm_roe_merge_chunks(pos))
+ break;
+ return ret;
+ }
+ if (pos->gpa > gpa) {
+ struct protected_chunk *prev;
+ prev = list_prev_entry(pos, list);
+ return kvm_roe_insert_chunk_next(&prev->list, gpa,
+ size);
+ }
+ }
+ pos = list_last_entry(head, struct protected_chunk, list);
+
+ return kvm_roe_insert_chunk_next(&pos->list, gpa, size);
+}
+static int kvm_roe_insert_chunk(struct kvm *kvm, u64 gpa, u64 size)
+{
+ struct kvm_memory_slot *slot;
+ gfn_t gfn = gpa >> PAGE_SHIFT;
+ int ret;
+
+ mutex_lock(&kvm->slots_lock);
+ slot = gfn_to_memslot(kvm, gfn);
+ ret = __kvm_roe_insert_chunk(slot, gpa, size);
+ mutex_unlock(&kvm->slots_lock);
+ return ret;
+}
+
+static int kvm_roe_partial_page_protect(struct kvm_vcpu *vcpu, u64 gva,
+ u64 size)
+{
+ gpa_t gpa = kvm_mmu_gva_to_gpa_system(vcpu, gva, NULL);
+
+ kvm_roe_protect_range(vcpu->kvm, gpa, 1, true);
+ return kvm_roe_insert_chunk(vcpu->kvm, gpa, size);
+}
+
+static int kvm_roe_partial_protect(struct kvm_vcpu *vcpu, u64 gva, u64 size)
+{
+ u64 gva_start = gva;
+ u64 gva_end = gva+size;
+ u64 gpn_start = gva_start >> PAGE_SHIFT;
+ u64 gpn_end = gva_end >> PAGE_SHIFT;
+ u64 _size;
+ int count = 0;
+ // We need to make sure that there will be no overflow or zero size
+ if (gva_end <= gva_start)
+ return -EINVAL;
+
+ // protect the partial page at the start
+ if (gpn_end > gpn_start)
+ _size = PAGE_SIZE - (gva_start & PAGE_MASK) + 1;
+ else
+ _size = size;
+ size -= _size;
+ count += kvm_roe_partial_page_protect(vcpu, gva_start, _size);
+ // full protect in the middle pages
+ if (gpn_end - gpn_start > 1) {
+ int ret;
+ u64 _gva = (gpn_start + 1) << PAGE_SHIFT;
+ u64 npages = gpn_end - gpn_start - 1;
+
+ size -= npages << PAGE_SHIFT;
+ ret = kvm_roe_full_protect_range(vcpu, _gva, npages);
+ if (ret > 0)
+ count += ret << PAGE_SHIFT;
+ }
+ // protect the partial page at the end
+ if (size != 0)
+ count += kvm_roe_partial_page_protect(vcpu,
+ gpn_end << PAGE_SHIFT, size);
+ if (count == 0)
+ return -EINVAL;
+ return count;
+}
static int kvm_roe(struct kvm_vcpu *vcpu, u64 a0, u64 a1, u64 a2, u64 a3)
{
int ret;
@@ -7045,11 +7180,14 @@ static int kvm_roe(struct kvm_vcpu *vcpu, u64 a0, u64 a1, u64 a2, u64 a3)
return -KVM_ENOSYS;
switch (a0) {
case ROE_VERSION:
- ret = 1; //current version
+ ret = 2; //current version
break;
case ROE_MPROTECT:
ret = kvm_roe_full_protect_range(vcpu, a1, a2);
break;
+ case ROE_MPROTECT_CHUNK:
+ ret = kvm_roe_partial_protect(vcpu, a1, a2);
+ break;
default:
ret = -EINVAL;
}
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index be6885bc28bc..a6749a52386b 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -294,11 +294,37 @@ static inline int kvm_vcpu_exiting_guest_mode(struct kvm_vcpu *vcpu)
*/
#define KVM_MEM_MAX_NR_PAGES ((1UL << 31) - 1)
+#ifdef CONFIG_KVM_ROE
+/*
+ * This structure is used to hold memory areas that are to be protected in a
+ * memory frame with mixed page permissions.
+ **/
+struct protected_chunk {
+ gpa_t gpa;
+ u64 size;
+ struct list_head list;
+};
+
+static inline bool kvm_roe_range_overlap(struct protected_chunk *chunk,
+ gpa_t gpa, int len) {
+ /*
+ * https://stackoverflow.com/questions/325933/
+ * determine-whether-two-date-ranges-overlap
+ * Assuming that it works, that link ^ provides a solution that is
+ * better than anything I would ever come up with.
+ */
+ return (gpa <= chunk->gpa + chunk->size - 1) &&
+ (gpa + len - 1 >= chunk->gpa);
+}
+#endif
+
struct kvm_memory_slot {
gfn_t base_gfn;
unsigned long npages;
#ifdef CONFIG_KVM_ROE
unsigned long *roe_bitmap;
+ unsigned long *partial_roe_bitmap;
+ struct list_head *prot_list;
#endif
unsigned long *dirty_bitmap;
struct kvm_arch_memory_slot arch;
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index e6004e0750fd..4a84f974bc58 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -33,6 +33,7 @@
/* ROE Functionality parameters */
#define ROE_VERSION 0
#define ROE_MPROTECT 1
+#define ROE_MPROTECT_CHUNK 2
/*
* hypercalls use architecture specific
*/
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index f9382a839361..2d3011e8490e 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -553,10 +553,19 @@ static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
struct kvm_memory_slot *dont)
{
#ifdef CONFIG_KVM_ROE
- if (!dont)
+ if (!dont) {
+ //TODO still this might leak
+ struct protected_chunk *pos, *n;
+ struct list_head *head = free->prot_list;
kvfree(free->roe_bitmap);
+ kvfree(free->partial_roe_bitmap);
+ list_for_each_entry_safe(pos, n, head, list) {
+ list_del(&pos->list);
+ kvfree(pos);
+ }
+ kvfree(free->prot_list);
+ }
#endif
-
if (!dont || free->dirty_bitmap != dont->dirty_bitmap)
kvm_destroy_dirty_bitmap(free);
@@ -803,13 +812,22 @@ static int kvm_create_dirty_bitmap(struct kvm_memory_slot *memslot)
return 0;
}
-static int kvm_init_roe_bitmap(struct kvm_memory_slot *slot)
+static int kvm_init_roe(struct kvm_memory_slot *slot)
{
#ifdef CONFIG_KVM_ROE
slot->roe_bitmap = kvzalloc(BITS_TO_LONGS(slot->npages) *
sizeof(unsigned long), GFP_KERNEL);
if (!slot->roe_bitmap)
return -ENOMEM;
+ slot->partial_roe_bitmap = kvzalloc(BITS_TO_LONGS(slot->npages) *
+ sizeof(unsigned long), GFP_KERNEL);
+ if (!slot->partial_roe_bitmap) {
+ kvfree(slot->roe_bitmap);
+ return -ENOMEM;
+ }
+ slot->prot_list = kvzalloc(sizeof(struct list_head), GFP_KERNEL);
+ INIT_LIST_HEAD(slot->prot_list);
+
#endif
return 0;
}
@@ -1036,7 +1054,7 @@ int __kvm_set_memory_region(struct kvm *kvm,
if (kvm_create_dirty_bitmap(&new) < 0)
goto out_free;
}
- if (kvm_init_roe_bitmap(&new) < 0)
+ if (kvm_init_roe(&new) < 0)
goto out_free;
slots = kvzalloc(sizeof(struct kvm_memslots), GFP_KERNEL);
@@ -1290,26 +1308,37 @@ static bool memslot_is_readonly(struct kvm_memory_slot *slot)
{
return slot->flags & KVM_MEM_READONLY;
}
+#ifdef CONFIG_KVM_ROE
+static bool gfn_is_partially_protected(struct kvm_memory_slot *slot, gfn_t gfn)
+{
+
+ return test_bit(gfn - slot->base_gfn, slot->partial_roe_bitmap);
+}
+static bool gfn_is_fully_protected(struct kvm_memory_slot *slot, gfn_t gfn)
+{
+ return test_bit(gfn - slot->base_gfn, slot->roe_bitmap);
+}
+#endif
static bool gfn_is_readonly(struct kvm_memory_slot *slot, gfn_t gfn)
{
#ifdef CONFIG_KVM_ROE
- return test_bit(gfn - slot->base_gfn, slot->roe_bitmap) ||
- memslot_is_readonly(slot);
+ return gfn_is_fully_protected(slot, gfn) ||
+ gfn_is_partially_protected(slot, gfn) ||
+ memslot_is_readonly(slot);
#else
return memslot_is_readonly(slot);
#endif
}
+
static unsigned long __gfn_to_hva_many(struct kvm_memory_slot *slot, gfn_t gfn,
gfn_t *nr_pages, bool write)
{
if (!slot || slot->flags & KVM_MEMSLOT_INVALID)
return KVM_HVA_ERR_BAD;
-
if (gfn_is_readonly(slot, gfn) && write)
return KVM_HVA_ERR_RO_BAD;
-
if (nr_pages)
*nr_pages = slot->npages - (gfn - slot->base_gfn);
@@ -1871,14 +1900,55 @@ int kvm_vcpu_read_guest_atomic(struct kvm_vcpu *vcpu, gpa_t gpa,
return __kvm_read_guest_atomic(slot, gfn, data, offset, len);
}
EXPORT_SYMBOL_GPL(kvm_vcpu_read_guest_atomic);
+#ifdef CONFIG_KVM_ROE
+static bool kvm_roe_protected_range(struct kvm_memory_slot *slot, gpa_t gpa,
+ int len)
+{
+ struct list_head *pos;
+ struct protected_chunk *cur_chunk;
+
+ list_for_each(pos, slot->prot_list) {
+ cur_chunk = list_entry(pos, struct protected_chunk, list);
+ if (kvm_roe_range_overlap(cur_chunk, gpa, len))
+ return true;
+ }
+ return false;
+}
+static bool kvm_roe_check_range(struct kvm_memory_slot *slot,
+ gfn_t gfn, int offset, int len)
+{
+ gpa_t gpa = (gfn << PAGE_SHIFT) + offset;
+
+ if (!gfn_is_partially_protected(slot, gfn))
+ return false;
+ return kvm_roe_protected_range(slot, gpa, len);
+}
+#endif
+static u64 roe_gfn_to_hva(struct kvm_memory_slot *slot, gfn_t gfn, int offset,
+ int len)
+{
+ u64 addr;
+#ifdef CONFIG_KVM_ROE
+ if (kvm_roe_check_range(slot, gfn, offset, len))
+ return KVM_HVA_ERR_RO_BAD;
+ if (memslot_is_readonly(slot))
+ return KVM_HVA_ERR_RO_BAD;
+ if (gfn_is_fully_protected(slot, gfn))
+ return KVM_HVA_ERR_RO_BAD;
+ addr = __gfn_to_hva_many(slot, gfn, NULL, false);
+#else
+ addr = gfn_to_hva_memslot(slot, gfn);
+#endif
+ return addr;
+}
static int __kvm_write_guest_page(struct kvm_memory_slot *memslot, gfn_t gfn,
const void *data, int offset, int len)
{
int r;
unsigned long addr;
- addr = gfn_to_hva_memslot(memslot, gfn);
+ addr = roe_gfn_to_hva(memslot, gfn, offset, len);
if (kvm_is_error_hva(addr))
return -EFAULT;
r = __copy_to_user((void __user *)addr + offset, data, len);
--
2.18.1
* Ahmed Abd El Mawgood <[email protected]> wrote:
> This is the 5th version which is 4th version with minor fixes. ROE is a
> hypercall that enables host operating system to restrict guest's access to its
> own memory. This will provide a hardening mechanism that can be used to stop
> rootkits from manipulating kernel static data structures and code. Once a memory
> region is protected the guest kernel can't even request undoing the protection.
>
> Memory protected by ROE should be non-swapable because even if the ROE protected
> page got swapped out, It won't be possible to write anything in its place.
>
> ROE hypercall should be capable of either protecting a whole memory frame or
> parts of it. With these two, it should be possible for guest kernel to protect
> its memory and all the page table entries for that memory inside the page table.
> I am still not sure whether this should be part of ROE job or the guest's job.
>
>
> The reason why it would be better to implement this from inside kvm: instead of
> (host) user space is the need to access SPTEs to modify the permissions, while
> mprotect() from user space can work in theory. It will become a big performance
> hit to vmexit and switch to user space mode on each fault, on the other hand,
> having the permission handled by EPT should make some remarkable performance
> gain.
>
> Our model assumes that an attacker got full root access to a running guest and
> his goal is to manipulate kernel code/data (hook syscalls, overwrite IDT ..etc).
>
> There is future work in progress to also put some sort of protection on the page
> table register CR3 and other critical registers that can be intercepted by KVM.
> This way it won't be possible for an attacker to manipulate any part of the
> guests page table.
BTW., transparent detection and trapping of attacks would also be nice:
if ROE is active and something running on the guest still attempts to
change the pagetables, the guest should be frozen and a syslog warning on
the hypervisor side should be printed?
Also, the feature should probably be 'default y' to help spread it on the
distro side. It's opt-in functionality from the guest side anyway, so
there's no real cost on the host side other than some minor resident
memory.
Thanks,
Ingo
On Fri, Oct 26, 2018 at 05:12:19PM +0200, Ahmed Abd El Mawgood wrote:
> Following up with my previous threads on KVM assisted Anti rootkit
> protections.
All of the changelogs in this series need to be rewritten to adhere to
Documentation/process[1]. In particular, use imperative mood and
describe why/what is being done in a self-contained manner, e.g. the
above line isn't very helpful without a lot of prior context.
[1] https://github.com/torvalds/linux/blob/master/Documentation/process/submitting-patches.rst#2-describe-your-changes
> The current version doesn't address the attacks involving pages
> remapping. It is still design in progress, nevertheless, it will be in
> my later patch sets.
This series should be tagged RFC if its design is a WIP.
On Fri, Oct 26, 2018 at 05:12:23PM +0200, Ahmed Abd El Mawgood wrote:
> This should be a little bit more readable and prone to memory leaks
Describe what is being, both in the subject line and continuing on in
the full changelog, e.g. "Small Refactoring to kvm_free_memslot" doesn't
provide any clue as to what is being done. And this is not what I would
describe as refactoring, e.g. verifying the new behavior means tracing
through its impact on __kvm_set_memory_region().
Lastly, this should be sent as a separate patch. There is no dependency
on the ROE code and if it actually addresses a potential memory leak (I
haven't actually reviewed the code itself) it should go in sooner rather
than later.
>
> Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
> ---
> virt/kvm/kvm_main.c | 15 +++++++--------
> 1 file changed, 7 insertions(+), 8 deletions(-)
>
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 2d3011e8490e..79c98db03c84 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -550,11 +550,11 @@ static void kvm_destroy_dirty_bitmap(struct kvm_memory_slot *memslot)
> * Free any memory in @free but not in @dont.
> */
> static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
> - struct kvm_memory_slot *dont)
> + struct kvm_memory_slot *dont,
> + enum kvm_mr_change change)
> {
> + if (change == KVM_MR_DELETE) {
> #ifdef CONFIG_KVM_ROE
> - if (!dont) {
> - //TODO still this might leak
> struct protected_chunk *pos, *n;
> struct list_head *head = free->prot_list;
> kvfree(free->roe_bitmap);
> @@ -564,10 +564,9 @@ static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *free,
> kvfree(pos);
> }
> kvfree(free->prot_list);
> - }
> #endif
> - if (!dont || free->dirty_bitmap != dont->dirty_bitmap)
> kvm_destroy_dirty_bitmap(free);
> + }
>
> kvm_arch_free_memslot(kvm, free, dont);
>
> @@ -582,7 +581,7 @@ static void kvm_free_memslots(struct kvm *kvm, struct kvm_memslots *slots)
> return;
>
> kvm_for_each_memslot(memslot, slots)
> - kvm_free_memslot(kvm, memslot, NULL);
> + kvm_free_memslot(kvm, memslot, NULL, KVM_MR_DELETE);
>
> kvfree(slots);
> }
> @@ -1100,14 +1099,14 @@ int __kvm_set_memory_region(struct kvm *kvm,
>
> kvm_arch_commit_memory_region(kvm, mem, &old, &new, change);
>
> - kvm_free_memslot(kvm, &old, &new);
> + kvm_free_memslot(kvm, &old, &new, change);
> kvfree(old_memslots);
> return 0;
>
> out_slots:
> kvfree(slots);
> out_free:
> - kvm_free_memslot(kvm, &new, &old);
> + kvm_free_memslot(kvm, &new, &old, change);
> out:
> return r;
> }
> --
> 2.18.1
>
Hi,
On 26/10/2018 16:12, Ahmed Abd El Mawgood wrote:
> This is the 5th version which is 4th version with minor fixes. ROE is a
> hypercall that enables host operating system to restrict guest's access to its
> own memory. This will provide a hardening mechanism that can be used to stop
> rootkits from manipulating kernel static data structures and code. Once a memory
> region is protected the guest kernel can't even request undoing the protection.
This is very interesting, because it seems a very good match to the work
I'm doing, for supporting the creation of more targets for protection:
https://www.openwall.com/lists/kernel-hardening/2018/10/23/3
In my case the protection would extend also to write-rate type of data.
There is an open problem of identifying legitimate write-rare
operations, however it should be possible to provide at least a certain
degree of confidence.
--
igor
On Mon, 29 Oct 2018 at 08:46, Ingo Molnar <[email protected]> wrote:
>
>
> * Ahmed Abd El Mawgood <[email protected]> wrote:
>
> > This is the 5th version which is 4th version with minor fixes. ROE is a
> > hypercall that enables host operating system to restrict guest's access to its
> > own memory. This will provide a hardening mechanism that can be used to stop
> > rootkits from manipulating kernel static data structures and code. Once a memory
> > region is protected the guest kernel can't even request undoing the protection.
> >
> > Memory protected by ROE should be non-swapable because even if the ROE protected
> > page got swapped out, It won't be possible to write anything in its place.
> >
> > ROE hypercall should be capable of either protecting a whole memory frame or
> > parts of it. With these two, it should be possible for guest kernel to protect
> > its memory and all the page table entries for that memory inside the page table.
> > I am still not sure whether this should be part of ROE job or the guest's job.
> >
> >
> > The reason why it would be better to implement this from inside kvm: instead of
> > (host) user space is the need to access SPTEs to modify the permissions, while
> > mprotect() from user space can work in theory. It will become a big performance
> > hit to vmexit and switch to user space mode on each fault, on the other hand,
> > having the permission handled by EPT should make some remarkable performance
> > gain.
> >
> > Our model assumes that an attacker got full root access to a running guest and
> > his goal is to manipulate kernel code/data (hook syscalls, overwrite IDT ..etc).
> >
> > There is future work in progress to also put some sort of protection on the page
> > table register CR3 and other critical registers that can be intercepted by KVM.
> > This way it won't be possible for an attacker to manipulate any part of the
> > guests page table.
>
> BTW., transparent detection and trapping of attacks would also be nice:
> if ROE is active and something running on the guest still attempts to
> change the pagetables, the guest should be frozen and a syslog warning on
> the hypervisor side should be printed?
I was thinking about logging ROE violations to host's kernel logs too
and I will have it in the next
version of the patch set, but I am not sure if we should really freeze
the guest once a violation
happens, I wanted to completely isolate the mechanism from the policy.
In the current implementation,
the policy followed is left to users pace host process using kvm. It
will be notified about a Memory IO
error in the guest, and the guest will be waiting for the host to take
actions (vmexit). Of course the
simplest action shall be "do nothing", so the writing operation will
fail but the guest continues to run, or it
can freeze the guest, or maybe do something advanced like cloning the
vm and disconnecting network
interface from the cloned vm, then enable the write operation and keep
track of what is going on somehow.
Because of the many varieties of possible reactions, we decided to
keep the them out of KVM and just let the
user space process decide how should the situation be handled.
> Also, the feature should probably be 'default y' to help spread it on the
> distro side. It's opt-in functionality from the guest side anyway, so
> there's no real cost on the host side other than some minor resident
> memory.
Noted, I will have it `default y` in the next patches set.
Thanks,
Ahmed
On 10/26/2018 05:12 PM, Ahmed Abd El Mawgood wrote:
> This is the 5th version which is 4th version with minor fixes. ROE is a
> hypercall that enables host operating system to restrict guest's access to its
> own memory. This will provide a hardening mechanism that can be used to stop
> rootkits from manipulating kernel static data structures and code. Once a memory
> region is protected the guest kernel can't even request undoing the protection.
At the KVM forum we considered this as something that could be implemented across
multiple architectures. Yes, there will be architecture specific implementations
and yes some architectures might be not able to provide everything (e.g. sub-page
granularity).
But we should really check if we can come up with a guest interface or guest common
code that can be useful across architectures.
>
> Memory protected by ROE should be non-swapable because even if the ROE protected
> page got swapped out, It won't be possible to write anything in its place.
>
> ROE hypercall should be capable of either protecting a whole memory frame or
> parts of it. With these two, it should be possible for guest kernel to protect
> its memory and all the page table entries for that memory inside the page table.
> I am still not sure whether this should be part of ROE job or the guest's job.
>
>
> The reason why it would be better to implement this from inside kvm: instead of
> (host) user space is the need to access SPTEs to modify the permissions, while
> mprotect() from user space can work in theory. It will become a big performance
> hit to vmexit and switch to user space mode on each fault, on the other hand,
> having the permission handled by EPT should make some remarkable performance
> gain.
>
> Our model assumes that an attacker got full root access to a running guest and
> his goal is to manipulate kernel code/data (hook syscalls, overwrite IDT ..etc).
>
> There is future work in progress to also put some sort of protection on the page
> table register CR3 and other critical registers that can be intercepted by KVM.
> This way it won't be possible for an attacker to manipulate any part of the
> guests page table.
>
> V4->V5 change log:
> - Fixed summary (it was reverted summary)
> - Fixed an inaccurate documentation in patch [4/5]
>
> Summary:
>
> Documentation/virtual/kvm/hypercalls.txt | 40 +++++
> arch/x86/include/asm/kvm_host.h | 11 +-
> arch/x86/kvm/Kconfig | 7 +
> arch/x86/kvm/mmu.c | 129 ++++++++++----
> arch/x86/kvm/vmx.c | 2 +-
> arch/x86/kvm/x86.c | 281 ++++++++++++++++++++++++++++++-
> include/linux/kvm_host.h | 29 ++++
> include/uapi/linux/kvm_para.h | 5 +
> virt/kvm/kvm_main.c | 119 +++++++++++--
> 9 files changed, 572 insertions(+), 51 deletions(-)
>
> Signed-off-by: Ahmed Abd El Mawgood <[email protected]>
>
On Mon, 29 Oct 2018 at 18:42, Sean Christopherson
<[email protected]> wrote:
>
> On Fri, Oct 26, 2018 at 05:12:19PM +0200, Ahmed Abd El Mawgood wrote:
> > Following up with my previous threads on KVM assisted Anti rootkit
> > protections.
>
> All of the changelogs in this series need to be rewritten to adhere to
> Documentation/process[1]. In particular, use imperative mood and
> describe why/what is being done in a self-contained manner, e.g. the
> above line isn't very helpful without a lot of prior context.
>
> [1] https://github.com/torvalds/linux/blob/master/Documentation/process/submitting-patches.rst#2-describe-your-changes
>
> > The current version doesn't address the attacks involving pages
> > remapping. It is still design in progress, nevertheless, it will be in
> > my later patch sets.
>
> This series should be tagged RFC if its design is a WIP.
Ops my bad, the current description is obsolete and I forgot to remove
it. I will have this fixed as well as your other comments in the next
patch series.
Hello Igor,
> This is very interesting, because it seems a very good match to the work
> I'm doing, for supporting the creation of more targets for protection:
>
> https://www.openwall.com/lists/kernel-hardening/2018/10/23/3
>
> In my case the protection would extend also to write-rate type of data.
> There is an open problem of identifying legitimate write-rare
> operations, however it should be possible to provide at least a certain
> degree of confidence.
I have checked your patch set. In our work we were originally planning to do
something similar to write_rare just so we can differentiate between memory
chunks that may be modified and those that will be set once and never modify.
I see you are planning to do a white paper too, actually we are doing
an academic
paper based on our work. If you would like to collaborate, so that ROE
and write_rare
would integrate well from the beginning, we will be glad to do so.
Thanks,
--
Ahmed
Junior Researcher , IoT and Cyber Security lab, SmartCI , Alexandria
University, & CIS @ VMI
Hello Ahmed,
On 01/11/2018 01:21, Ahmed Soliman wrote:
> Hello Igor,
>> This is very interesting, because it seems a very good match to the work
>> I'm doing, for supporting the creation of more targets for protection:
>>
>> https://www.openwall.com/lists/kernel-hardening/2018/10/23/3
>>
>> In my case the protection would extend also to write-rate type of data.
>> There is an open problem of identifying legitimate write-rare
>> operations, however it should be possible to provide at least a certain
>> degree of confidence.
>
> I have checked your patch set. In our work we were originally planning to do
> something similar to write_rare just so we can differentiate between memory
> chunks that may be modified and those that will be set once and never modify.
> I see you are planning to do a white paper too, actually we are doing
> an academic
> paper based on our work. If you would like to collaborate, so that ROE
> and write_rare
> would integrate well from the beginning, we will be glad to do so.
The offer is very kind, thanks a lot.
I will contact you in private.
--
igor