[PATCH] KVM: ARM: 16-bit Thumb instructions shouldn't generate error

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



When copying guest instructions on an 'invalid' IO abort, we don't want
to just greedily copy 32 bits, as a 16-bit thumb instruction at the end
of a page may exist such that the following page is not mapped in the
guest and our attempt to translate the VA->IPA of that page will just
continously fail.  No, instead we do this madness twice, checking the
length of the instruciton in between.

Signed-off-by: Christoffer Dall <c.dall@xxxxxxxxxxxxxxxxxxxxxx>
---
 arch/arm/kvm/mmu.c |   83 ++++++++++++++++++++++++++++------------------------
 1 file changed, 45 insertions(+), 38 deletions(-)

diff --git a/arch/arm/kvm/mmu.c b/arch/arm/kvm/mmu.c
index 025700d..3885ca9 100644
--- a/arch/arm/kvm/mmu.c
+++ b/arch/arm/kvm/mmu.c
@@ -586,7 +586,8 @@ int kvm_handle_mmio_return(struct kvm_vcpu *vcpu, struct kvm_run *run)
  * @dest:	memory to copy into
  * @va:		virtual address in guest to copy from
  * @len:	length to copy
- * @priv:	use guest PL1 (ie. kernel) mappings, otherwise use guest PL0 mappings.
+ * @priv:	use guest PL1 (ie. kernel) mappings
+ *              otherwise use guest PL0 mappings.
  *
  * Returns true on success, false on failure (unlikely, but retry).
  */
@@ -596,43 +597,32 @@ static bool copy_from_guest_va(struct kvm_vcpu *vcpu,
 {
 	u64 par;
 	phys_addr_t pc_ipa;
-	size_t cur_len;
 	int err;
 
-	/* A 32-bit thumb2 instruction can actually go over a page boundary! */
-	do {
-		par = __kvm_va_to_pa(vcpu, va & PAGE_MASK, priv);
-		if (par & 1) {
-			kvm_err("I/O Abort from invalid instruction address"
-				" %#lx!\n", va);
-			return false;
-		}
-
-		if (par & (1U << 11)) {
-			/* LPAE PAR format */
-			/*
-			 * TODO: Check if this ever happens
-			 * - called from Hyp mode
-			 */
-			pc_ipa = par & PAGE_MASK & ((1ULL << 32) - 1);
-		} else {
-			/* VMSAv7 PAR format */
-			pc_ipa = par & PAGE_MASK & ((1ULL << 40) - 1);
-		}
-		pc_ipa += va & ~PAGE_MASK;
-
-		/* Only copy up to end of page. */
-		cur_len = (pc_ipa & PAGE_MASK) + PAGE_SIZE - pc_ipa;
-		if (likely(cur_len > len))
-			cur_len = len;
+	BUG_ON((va & PAGE_MASK) != ((va + len) & PAGE_MASK));
+	par = __kvm_va_to_pa(vcpu, va & PAGE_MASK, priv);
+	if (par & 1) {
+		kvm_err("I/O Abort from invalid instruction address"
+			" %#lx!\n", va);
+		return false;
+	}
 
-		err = kvm_read_guest(vcpu->kvm, pc_ipa, dest, cur_len);
-		if (unlikely(err))
-			return false;
+	if (par & (1U << 11)) {
+		/* LPAE PAR format */
+		/*
+		 * TODO: Check if this ever happens
+		 * - called from Hyp mode
+		 */
+		pc_ipa = par & PAGE_MASK & ((1ULL << 32) - 1);
+	} else {
+		/* VMSAv7 PAR format */
+		pc_ipa = par & PAGE_MASK & ((1ULL << 40) - 1);
+	}
+	pc_ipa += va & ~PAGE_MASK;
 
-		len -= cur_len;
-		va += cur_len;
-	} while (unlikely(len != 0));
+	err = kvm_read_guest(vcpu->kvm, pc_ipa, dest, len);
+	if (unlikely(err))
+		return false;
 
 	return true;
 }
@@ -652,12 +642,13 @@ static void do_nothing(void *info)
  * Fortunately this is so rare (we don't usually need the instruction), we
  * can go very slowly and noone will mind.
  */
-static bool copy_current_insn(struct kvm_vcpu *vcpu,
-			     unsigned long *instr, size_t instr_len)
+static bool copy_current_insn(struct kvm_vcpu *vcpu, unsigned long *instr)
 {
 	int i;
 	bool ret;
 	struct kvm_vcpu *v;
+	bool is_thumb;
+	size_t instr_len;
 
 	/* Don't cross with IPIs in kvm_main.c */
 	spin_lock(&vcpu->kvm->mmu_lock);
@@ -676,10 +667,26 @@ static bool copy_current_insn(struct kvm_vcpu *vcpu,
 			smp_call_function_single(v->cpu, do_nothing, NULL, 1);
 	}
 
+
+	is_thumb = !!(*vcpu_cpsr(vcpu) & PSR_T_BIT);
+	instr_len = (is_thumb) ? 2 : 4;
+
+	BUG_ON(!is_thumb && vcpu->arch.regs.pc & 0x3);
+
 	/* Now guest isn't running, we can va->pa map and copy atomically. */
 	ret = copy_from_guest_va(vcpu, instr, vcpu->arch.regs.pc, instr_len,
 				 vcpu_mode_priv(vcpu));
+	if (!ret)
+		goto out;
 
+	/* A 32-bit thumb2 instruction can actually go over a page boundary! */
+	if (is_thumb && is_wide_instruction(*instr)) {
+		*instr = *instr << 16;
+		ret = copy_from_guest_va(vcpu, instr, vcpu->arch.regs.pc, 2,
+					 vcpu_mode_priv(vcpu));
+	}
+
+out:
 	/* Release them all. */
 	kvm_for_each_vcpu(i, v, vcpu->kvm)
 		v->arch.pause = false;
@@ -702,7 +709,7 @@ static bool copy_current_insn(struct kvm_vcpu *vcpu,
  */
 static int invalid_io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
 {
-	unsigned long instr;
+	unsigned long instr = 0;
 
 	if (vcpu->arch.regs.cpsr & PSR_T_BIT) {
 		/* TODO: Check validity of PC IPA and IPA2!!! */
@@ -712,7 +719,7 @@ static int invalid_io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
 	}
 
 	/* If it fails (SMP race?), we reenter guest for it to retry. */
-	if (!copy_current_insn(vcpu, &instr, sizeof(instr)))
+	if (!copy_current_insn(vcpu, &instr))
 		return 1;
 
 	return kvm_emulate_mmio_ls(vcpu, fault_ipa, instr);
-- 
1.7.9.5

_______________________________________________
kvmarm mailing list
kvmarm@xxxxxxxxxxxxxxxxxxxxx
https://lists.cs.columbia.edu/cucslists/listinfo/kvmarm


[Index of Archives]     [Linux KVM]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux