[RFC,v6,66/92] kvm: introspection: add custom input when single-stepping a vCPU
diff mbox series

Message ID 20190809160047.8319-67-alazar@bitdefender.com
State New
Headers show
Series
  • VM introspection
Related show

Commit Message

Adalbert Lazăr Aug. 9, 2019, 4 p.m. UTC
The introspection tool can respond to a KVMI_EVENT_PF event with custom
input for the current instruction. This input is used to trick the guest
software into believing it has read certain data, in order to hide the
content of certain memory areas (eg. hide injected code from integrity
checkers). There are cases when this can happen while the vCPU has to
be single stepped, Either the current instruction is not supported by
the KVM emulator or the introspection tool requested single-stepping.

This patch saves the old data, write the custom input, start the single
stepping and restore the old data.

Signed-off-by: Adalbert Lazăr <alazar@bitdefender.com>
---
 virt/kvm/kvmi.c     | 119 ++++++++++++++++++++++++++++++++++++++++++++
 virt/kvm/kvmi_int.h |   3 ++
 2 files changed, 122 insertions(+)

Patch
diff mbox series

diff --git a/virt/kvm/kvmi.c b/virt/kvm/kvmi.c
index 3dfedf3ae739..06dc23f40ded 100644
--- a/virt/kvm/kvmi.c
+++ b/virt/kvm/kvmi.c
@@ -1618,6 +1618,116 @@  int kvmi_cmd_pause_vcpu(struct kvm_vcpu *vcpu, bool wait)
 	return 0;
 }
 
+static int write_custom_data_to_page(struct kvm_vcpu *vcpu, gva_t gva,
+					u8 *backup, size_t bytes)
+{
+	u8 *ptr_page, *ptr;
+	struct page *page;
+	gpa_t gpa;
+
+	gpa = kvm_mmu_gva_to_gpa_system(vcpu, gva, NULL);
+	if (gpa == UNMAPPED_GVA)
+		return -KVM_EINVAL;
+
+	ptr_page = get_page_ptr(vcpu->kvm, gpa, &page, true);
+	if (!ptr_page)
+		return -KVM_EINVAL;
+
+	ptr = ptr_page + (gpa & ~PAGE_MASK);
+
+	memcpy(backup, ptr, bytes);
+	use_custom_input(vcpu, gva, ptr, bytes);
+
+	put_page_ptr(ptr_page, page);
+
+	return 0;
+}
+
+static int write_custom_data(struct kvm_vcpu *vcpu)
+{
+	struct kvmi *ikvm = IKVM(vcpu->kvm);
+	struct kvmi_vcpu *ivcpu = IVCPU(vcpu);
+	size_t bytes = ivcpu->ctx_size;
+	gva_t gva = ivcpu->ctx_addr;
+	u8 *backup;
+
+	if (ikvm->ss_custom_size)
+		return 0;
+
+	if (!bytes)
+		return 0;
+
+	backup = ikvm->ss_custom_data;
+
+	while (bytes) {
+		size_t offset = gva & ~PAGE_MASK;
+		size_t chunk = min(bytes, PAGE_SIZE - offset);
+
+		if (write_custom_data_to_page(vcpu, gva, backup, chunk))
+			return -KVM_EINVAL;
+
+		bytes -= chunk;
+		backup += chunk;
+		gva += chunk;
+		ikvm->ss_custom_size += chunk;
+	}
+
+	return 0;
+}
+
+static int restore_backup_data_to_page(struct kvm_vcpu *vcpu, gva_t gva,
+					u8 *src, size_t bytes)
+{
+	u8 *ptr_page, *ptr;
+	struct page *page;
+	gpa_t gpa;
+
+	gpa = kvm_mmu_gva_to_gpa_system(vcpu, gva, NULL);
+	if (gpa == UNMAPPED_GVA)
+		return -KVM_EINVAL;
+
+	ptr_page = get_page_ptr(vcpu->kvm, gpa, &page, true);
+	if (!ptr_page)
+		return -KVM_EINVAL;
+
+	ptr = ptr_page + (gpa & ~PAGE_MASK);
+
+	memcpy(ptr, src, bytes);
+
+	put_page_ptr(ptr_page, page);
+
+	return 0;
+}
+
+static void restore_backup_data(struct kvm_vcpu *vcpu)
+{
+	struct kvmi *ikvm = IKVM(vcpu->kvm);
+	struct kvmi_vcpu *ivcpu = IVCPU(vcpu);
+	size_t bytes = ikvm->ss_custom_size;
+	gva_t gva = ivcpu->ctx_addr;
+	u8 *backup;
+
+	if (!bytes)
+		return;
+
+	backup = ikvm->ss_custom_data;
+
+	while (bytes) {
+		size_t offset = gva & ~PAGE_MASK;
+		size_t chunk = min(bytes, PAGE_SIZE - offset);
+
+		if (restore_backup_data_to_page(vcpu, gva, backup, chunk))
+			goto out;
+
+		bytes -= chunk;
+		backup += chunk;
+		gva += chunk;
+	}
+
+out:
+	ikvm->ss_custom_size = 0;
+}
+
 void kvmi_stop_ss(struct kvm_vcpu *vcpu)
 {
 	struct kvmi_vcpu *ivcpu = IVCPU(vcpu);
@@ -1642,6 +1752,8 @@  void kvmi_stop_ss(struct kvm_vcpu *vcpu)
 
 	ikvm->ss_level = 0;
 
+	restore_backup_data(vcpu);
+
 	kvmi_arch_stop_single_step(vcpu);
 
 	atomic_set(&ikvm->ss_active, false);
@@ -1676,6 +1788,7 @@  static bool kvmi_acquire_ss(struct kvm_vcpu *vcpu)
 						KVM_REQUEST_WAIT);
 
 	ivcpu->ss_owner = true;
+	ikvm->ss_custom_size = 0;
 
 	return true;
 }
@@ -1690,6 +1803,12 @@  static bool kvmi_run_ss(struct kvm_vcpu *vcpu, gpa_t gpa, u8 access)
 
 	kvmi_arch_start_single_step(vcpu);
 
+	err = write_custom_data(vcpu);
+	if (err) {
+		kvmi_err(ikvm, "writing custom data failed, err %d\n", err);
+		return false;
+	}
+
 	err = kvmi_get_gfn_access(ikvm, gfn, &old_access, &old_write_bitmap);
 	/* likely was removed from radix tree due to rwx */
 	if (err) {
diff --git a/virt/kvm/kvmi_int.h b/virt/kvm/kvmi_int.h
index 1550fe33ed48..5485529db06b 100644
--- a/virt/kvm/kvmi_int.h
+++ b/virt/kvm/kvmi_int.h
@@ -160,6 +160,9 @@  struct kvmi {
 		u8 old_access;
 		u32 old_write_bitmap;
 	} ss_context[SINGLE_STEP_MAX_DEPTH];
+	u8 ss_custom_data[KVMI_CTX_DATA_SIZE];
+	size_t ss_custom_size;
+	gpa_t ss_custom_addr;
 	u8 ss_level;
 	atomic_t ss_active;