KVM: x86 emulator: implement segment permission checks
authorAvi Kivity <avi@redhat.com>
Sun, 3 Apr 2011 09:32:09 +0000 (12:32 +0300)
committerAvi Kivity <avi@redhat.com>
Wed, 11 May 2011 11:57:08 +0000 (07:57 -0400)
Signed-off-by: Avi Kivity <avi@redhat.com>
arch/x86/kvm/emulate.c

index 793aff5..2ec69bc 100644 (file)
@@ -515,6 +515,11 @@ static int emulate_gp(struct x86_emulate_ctxt *ctxt, int err)
        return emulate_exception(ctxt, GP_VECTOR, err, true);
 }
 
+static int emulate_ss(struct x86_emulate_ctxt *ctxt, int err)
+{
+       return emulate_exception(ctxt, SS_VECTOR, err, true);
+}
+
 static int emulate_ud(struct x86_emulate_ctxt *ctxt)
 {
        return emulate_exception(ctxt, UD_VECTOR, 0, false);
@@ -541,13 +546,71 @@ static int linearize(struct x86_emulate_ctxt *ctxt,
                     ulong *linear)
 {
        struct decode_cache *c = &ctxt->decode;
+       struct desc_struct desc;
+       bool usable;
        ulong la;
+       u32 lim;
+       unsigned cpl, rpl;
 
        la = seg_base(ctxt, ctxt->ops, addr.seg) + addr.ea;
+       switch (ctxt->mode) {
+       case X86EMUL_MODE_REAL:
+               break;
+       case X86EMUL_MODE_PROT64:
+               if (((signed long)la << 16) >> 16 != la)
+                       return emulate_gp(ctxt, 0);
+               break;
+       default:
+               usable = ctxt->ops->get_cached_descriptor(&desc, NULL, addr.seg,
+                                                         ctxt->vcpu);
+               if (!usable)
+                       goto bad;
+               /* code segment or read-only data segment */
+               if (((desc.type & 8) || !(desc.type & 2)) && write)
+                       goto bad;
+               /* unreadable code segment */
+               if ((desc.type & 8) && !(desc.type & 2))
+                       goto bad;
+               lim = desc_limit_scaled(&desc);
+               if ((desc.type & 8) || !(desc.type & 4)) {
+                       /* expand-up segment */
+                       if (addr.ea > lim || (u32)(addr.ea + size - 1) > lim)
+                               goto bad;
+               } else {
+                       /* exapand-down segment */
+                       if (addr.ea <= lim || (u32)(addr.ea + size - 1) <= lim)
+                               goto bad;
+                       lim = desc.d ? 0xffffffff : 0xffff;
+                       if (addr.ea > lim || (u32)(addr.ea + size - 1) > lim)
+                               goto bad;
+               }
+               cpl = ctxt->ops->cpl(ctxt->vcpu);
+               rpl = ctxt->ops->get_segment_selector(addr.seg, ctxt->vcpu) & 3;
+               cpl = max(cpl, rpl);
+               if (!(desc.type & 8)) {
+                       /* data segment */
+                       if (cpl > desc.dpl)
+                               goto bad;
+               } else if ((desc.type & 8) && !(desc.type & 4)) {
+                       /* nonconforming code segment */
+                       if (cpl != desc.dpl)
+                               goto bad;
+               } else if ((desc.type & 8) && (desc.type & 4)) {
+                       /* conforming code segment */
+                       if (cpl < desc.dpl)
+                               goto bad;
+               }
+               break;
+       }
        if (c->ad_bytes != 8)
                la &= (u32)-1;
        *linear = la;
        return X86EMUL_CONTINUE;
+bad:
+       if (addr.seg == VCPU_SREG_SS)
+               return emulate_ss(ctxt, addr.seg);
+       else
+               return emulate_gp(ctxt, addr.seg);
 }
 
 static int segmented_read_std(struct x86_emulate_ctxt *ctxt,