aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAvi Kivity <avi@qumranet.com>2007-03-28 14:04:16 -0400
committerAvi Kivity <avi@qumranet.com>2007-05-03 03:52:28 -0400
commitdf513e2cdd099822ed32cbc20aaf4ff310372202 (patch)
treef99a1cabbe88886ec522179172f02903a2a36e26
parentafeb1f14c5478560262b37431726eb0eb1a42e9e (diff)
KVM: x86 emulator: fix bit string operations operand size
On x86, bit operations operate on a string of bits that can reside in multiple words. For example, 'btsl %eax, (blah)' will touch the word at blah+4 if %eax is between 32 and 63. The x86 emulator compensates for that by advancing the operand address by (bit offset / BITS_PER_LONG) and truncating the bit offset to the range (0..BITS_PER_LONG-1). This has a side effect of forcing the operand size to 8 bytes on 64-bit hosts. Now, a 32-bit guest goes and fork()s a process. It write protects a stack page at 0xbffff000 using the 'btr' instruction, at offset 0xffc in the page table, with bit offset 1 (for the write permission bit). The emulator now forces the operand size to 8 bytes as previously described, and an innocent page table update turns into a cross-page-boundary write, which is assumed by the mmu code not to be a page table, so it doesn't actually clear the corresponding shadow page table entry. The guest and host permissions are out of sync and guest memory is corrupted soon afterwards, leading to guest failure. Fix by not using BITS_PER_LONG as the word size; instead use the actual operand size, so we get a 32-bit write in that case. Note we still have to teach the mmu to handle cross-page-boundary writes to guest page table; but for now this allows Damn Small Linux 0.4 (2.4.20) to boot. Signed-off-by: Avi Kivity <avi@qumranet.com>
-rw-r--r--drivers/kvm/x86_emulate.c5
1 files changed, 3 insertions, 2 deletions
diff --git a/drivers/kvm/x86_emulate.c b/drivers/kvm/x86_emulate.c
index 7513cddb929f..bcf872bdaf74 100644
--- a/drivers/kvm/x86_emulate.c
+++ b/drivers/kvm/x86_emulate.c
@@ -833,8 +833,9 @@ done_prefixes:
833 dst.ptr = (unsigned long *)cr2; 833 dst.ptr = (unsigned long *)cr2;
834 dst.bytes = (d & ByteOp) ? 1 : op_bytes; 834 dst.bytes = (d & ByteOp) ? 1 : op_bytes;
835 if (d & BitOp) { 835 if (d & BitOp) {
836 dst.ptr += src.val / BITS_PER_LONG; 836 unsigned long mask = ~(dst.bytes * 8 - 1);
837 dst.bytes = sizeof(long); 837
838 dst.ptr = (void *)dst.ptr + (src.val & mask) / 8;
838 } 839 }
839 if (!(d & Mov) && /* optimisation - avoid slow emulated read */ 840 if (!(d & Mov) && /* optimisation - avoid slow emulated read */
840 ((rc = ops->read_emulated((unsigned long)dst.ptr, 841 ((rc = ops->read_emulated((unsigned long)dst.ptr,