All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] x86 segment limits enforcement with TCG
@ 2019-02-24 19:36 Stephen Checkoway
  2019-02-24 19:46 ` Peter Maydell
  0 siblings, 1 reply; 10+ messages in thread
From: Stephen Checkoway @ 2019-02-24 19:36 UTC (permalink / raw)
  To: qemu-devel

Hi all,

Sorry for the long email. The TL;DR is I'm trying to add x86 segment limit checks in TCG and my added code is causing qemu to abort during code generation because some registers have become dead.

I have some x86 firmware that refuses to boot unless a variety of x86 security mechanisms are enforced. In particular, it checks that the general protection fault handler runs if an access to memory is greater than a segment limit: it configures a segment with a limit of 3, sets ds to the corresponding selector and then tries to load 4 bytes from address 3.

I looked through target/i386/translate.c and it looks like in most (all?) cases, gen_lea_v_seg is used, either directly or indirectly, to compute a linear address by adding the base address of the segment. I attempted to modify this to check the segment limit and raise a general protection fault if the base address plus the size of the access is greater than the limit. The full diff is below, but the main change adds this.

        if (s->pe && !CODE64(s)) {
            /*
             * Check that the access is within the segment limit in protected
             * mode.
             */
            TCGLabel *label = gen_new_label();
            int access_limit = (1 << (aflag & MO_SIZE)) - 1;
            tcg_gen_mov_tl(s->A1, a0);
            if (access_limit > 0) {
                tcg_gen_addi_tl(s->A1, s->A1, access_limit);
            }
            tcg_gen_brcond_tl(TCG_COND_LE, s->A1, limit, label);
            gen_exception(s, EXCP0D_GPF, s->pc_start - s->cs_base);
            gen_set_label(label);
        }

With this change, qemu aborts when trying to run tests/pxe-test. I ran the test in lldb and it's aborting in the TCG generation (full stack trace below).

frame #3: 0x000000010001eac4 qemu-system-i386`temp_load(s=0x0000000105062600, ts=0x0000000105063170, desired_regs=65535, allocated_regs=48, preferred_regs=0) at tcg.c:3211
   3208         break;
   3209     case TEMP_VAL_DEAD:
   3210     default:
-> 3211         tcg_abort();
   3212     }
   3213     ts->reg = reg;
   3214     ts->val_type = TEMP_VAL_REG;

because ts->value_type is TEMP_VAL_DEAD.

I tried removing everything except for the tcg_gen_brcond_tl (using a0, rather than s->A1) and the gen_set_label and I have the same behavior. The operation it aborts on is

(lldb) p *op
(TCGOp) $0 = {
  opc = INDEX_op_add_i32
  param1 = 0
  param2 = 0
  life = 24
  link = {
    tqe_next = 0x0000000105055c30
    tqe_circ = {
      tql_next = 0x0000000105055c30
      tql_prev = 0x0000000105055b48
    }
  }
  args = ([0] = 4378961264, [1] = 4378961264, [2] = 4378960256, [3] = 18446744073709486096, [4] = 281470681808641, [5] = 4378961880, [6] = 4378961880, [7] = 0, [8] = 18446744069414649855, [9] = 5, [10] = 4379204752)
  output_pref = ([0] = 65343, [1] = 1)
}

which I think is coming from the add for the segment base. Changing the condition to TCG_COND_ALWAYS breaks elsewhere. 

I think that something about adding the tcg_gen_brcond_tl is causing values to become dead and then qemu aborts.

My questions are
1. Is this the right place to add segment limit checks? (I guess I don't need to check every memory access; catching the majority of them is likely fine.)

2. Am I using tcg_gen_brcond_tl (or any of the other TCG functions) incorrectly?

3. Is there a way I can see the generated TCG for this test? I've been running lldb using
$ QTEST_QEMU_BINARY='tmux split-window lldb -- i386-softmmu/qemu-system-i386' QTEST_QEMU_IMG=qemu-img tests/pxe-test -m=quick -k --tap
but when I try to add additional options like -d op, the test harness aborts: qemu/tests/boot-sector.c:119:boot_sector_init: code should not be reached

Any assistance is very much appreciated.

Thank you,

Steve

Stack trace:
  * frame #0: 0x00007fff6b45323e libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff6b509c1c libsystem_pthread.dylib`pthread_kill + 285
    frame #2: 0x00007fff6b3bc1c9 libsystem_c.dylib`abort + 127
    frame #3: 0x000000010001eac4 qemu-system-i386`temp_load(s=0x0000000105062600, ts=0x0000000105063170, desired_regs=65535, allocated_regs=48, preferred_regs=0) at tcg.c:3211
    frame #4: 0x000000010001af09 qemu-system-i386`tcg_reg_alloc_op(s=0x0000000105062600, op=0x0000000104a78300) at tcg.c:3458
    frame #5: 0x00000001000176f2 qemu-system-i386`tcg_gen_code(s=0x0000000105062600, tb=0x00000001058f93c0) at tcg.c:3981
    frame #6: 0x00000001000d3972 qemu-system-i386`tb_gen_code(cpu=0x0000000105043c00, pc=787953, cs_base=786432, flags=192, cflags=-16252928) at translate-all.c:1751
    frame #7: 0x00000001000d1696 qemu-system-i386`tb_find(cpu=0x0000000105043c00, last_tb=0x0000000000000000, tb_exit=0, cf_mask=524288) at cpu-exec.c:407
    frame #8: 0x00000001000d0dc1 qemu-system-i386`cpu_exec(cpu=0x0000000105043c00) at cpu-exec.c:728
    frame #9: 0x000000010006bbf1 qemu-system-i386`tcg_cpu_exec(cpu=0x0000000105043c00) at cpus.c:1429
    frame #10: 0x000000010006b4b7 qemu-system-i386`qemu_tcg_cpu_thread_fn(arg=0x0000000105043c00) at cpus.c:1733
    frame #11: 0x00000001006223ec qemu-system-i386`qemu_thread_start(args=0x0000000103d0e770) at qemu-thread-posix.c:502
    frame #12: 0x00007fff6b507305 libsystem_pthread.dylib`_pthread_body + 126
    frame #13: 0x00007fff6b50a26f libsystem_pthread.dylib`_pthread_start + 70
    frame #14: 0x00007fff6b506415 libsystem_pthread.dylib`thread_start + 13

diff --git a/target/i386/translate.c b/target/i386/translate.c
index 49cd298374..6f3682a268 100644
--- a/target/i386/translate.c
+++ b/target/i386/translate.c
@@ -76,6 +76,7 @@ static TCGv cpu_cc_dst, cpu_cc_src, cpu_cc_src2;
 static TCGv_i32 cpu_cc_op;
 static TCGv cpu_regs[CPU_NB_REGS];
 static TCGv cpu_seg_base[6];
+static TCGv cpu_seg_limit[6];
 static TCGv_i64 cpu_bndl[4];
 static TCGv_i64 cpu_bndu[4];

@@ -130,6 +131,7 @@ typedef struct DisasContext {
     /* TCG local temps */
     TCGv cc_srcT;
     TCGv A0;
+    TCGv A1;
     TCGv T0;
     TCGv T1;

@@ -448,6 +450,8 @@ static inline void gen_jmp_im(DisasContext *s, target_ulong pc)
     gen_op_jmp_v(s->tmp0);
 }

+static void gen_exception(DisasContext *s, int trapno, target_ulong cur_eip);
+
 /* Compute SEG:REG into A0.  SEG is selected from the override segment
    (OVR_SEG) and the default segment (DEF_SEG).  OVR_SEG may be -1 to
    indicate no override.  */
@@ -491,6 +495,23 @@ static void gen_lea_v_seg(DisasContext *s, TCGMemOp aflag, TCGv a0,

     if (ovr_seg >= 0) {
         TCGv seg = cpu_seg_base[ovr_seg];
+        TCGv limit = cpu_seg_limit[ovr_seg];
+
+        if (s->pe && !CODE64(s)) {
+            /*
+             * Check that the access is within the segment limit in protected
+             * mode.
+             */
+            TCGLabel *label = gen_new_label();
+            int access_limit = (1 << (aflag & MO_SIZE)) - 1;
+            tcg_gen_mov_tl(s->A1, a0);
+            if (access_limit > 0) {
+                tcg_gen_addi_tl(s->A1, s->A1, access_limit);
+            }
+            tcg_gen_brcond_tl(TCG_COND_LE, s->A1, limit, label);
+            gen_exception(s, EXCP0D_GPF, s->pc_start - s->cs_base);
+            gen_set_label(label);
+        }

         if (aflag == MO_64) {
             tcg_gen_add_tl(s->A0, a0, seg);
@@ -8382,6 +8403,14 @@ void tcg_x86_init(void)
         [R_GS] = "gs_base",
         [R_SS] = "ss_base",
     };
+    static const char seg_limit_names[6][9] = {
+        [R_CS] = "cs_limit",
+        [R_DS] = "ds_limit",
+        [R_ES] = "es_limit",
+        [R_FS] = "fs_limit",
+        [R_GS] = "gs_limit",
+        [R_SS] = "ss_limit",
+    };
     static const char bnd_regl_names[4][8] = {
         "bnd0_lb", "bnd1_lb", "bnd2_lb", "bnd3_lb"
     };
@@ -8410,6 +8439,10 @@ void tcg_x86_init(void)
             = tcg_global_mem_new(cpu_env,
                                  offsetof(CPUX86State, segs[i].base),
                                  seg_base_names[i]);
+        cpu_seg_limit[i]
+            = tcg_global_mem_new(cpu_env,
+                                 offsetof(CPUX86State, segs[i].limit),
+                                 seg_limit_names[i]);
     }

     for (i = 0; i < 4; ++i) {
@@ -8482,6 +8515,7 @@ static void i386_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cpu)
     dc->T0 = tcg_temp_new();
     dc->T1 = tcg_temp_new();
     dc->A0 = tcg_temp_new();
+    dc->A1 = tcg_temp_new();

     dc->tmp0 = tcg_temp_new();
     dc->tmp1_i64 = tcg_temp_new_i64();

-- 
Stephen Checkoway

^ permalink raw reply related	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2019-03-07  1:49 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-02-24 19:36 [Qemu-devel] x86 segment limits enforcement with TCG Stephen Checkoway
2019-02-24 19:46 ` Peter Maydell
2019-02-24 20:21   ` Stephen Checkoway
2019-02-26  0:32     ` Stephen Checkoway
2019-02-26 16:56       ` Richard Henderson
2019-02-28 15:01         ` Stephen Checkoway
2019-02-28 16:11           ` Richard Henderson
2019-02-28 17:18             ` Stephen Checkoway
2019-02-28 18:05               ` Richard Henderson
2019-03-07  1:49                 ` Emilio G. Cota

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.