[v2] x86emul/fuzz: add a state sanitization function
diff mbox series

Message ID 5CA1C1CE02000078002236C0@prv1-mh.provo.novell.com
State New, archived
Headers show
Series
  • [v2] x86emul/fuzz: add a state sanitization function
Related show

Commit Message

Jan Beulich April 1, 2019, 7:46 a.m. UTC
This is to accompany sanitize_input(). Just like for initial state we
want to have state between two emulated insns sane, at least as far as
assumptions in the main emulator go. Do minimal checking after segment
register, CR, and MSR writes, and roll back to the old value in case of
failure (raising #GP(0) at the same time).

In the particular case observed, a CR0 write clearing CR0.PE was
followed by a VEX-encoded insn, which the decoder accepts based on
guest address size, restricting things just outside of the 64-bit case
(real and virtual modes don't allow VEX-encoded insns). Subsequently
_get_fpu() would then assert that CR0.PE must be set (and EFLAGS.VM
clear) when trying to invoke YMM, ZMM, or OPMASK state.

Signed-off-by: Jan Beulich <jbeulich@suse.com>
---
v2: Correct placement of new declaration in fuzz_write_segment().

Comments

George Dunlap April 1, 2019, 10:44 a.m. UTC | #1
> On Apr 1, 2019, at 8:46 AM, Jan Beulich <JBeulich@suse.com> wrote:
> 
> This is to accompany sanitize_input(). Just like for initial state we
> want to have state between two emulated insns sane, at least as far as
> assumptions in the main emulator go. Do minimal checking after segment
> register, CR, and MSR writes, and roll back to the old value in case of
> failure (raising #GP(0) at the same time).
> 
> In the particular case observed, a CR0 write clearing CR0.PE was
> followed by a VEX-encoded insn, which the decoder accepts based on
> guest address size, restricting things just outside of the 64-bit case
> (real and virtual modes don't allow VEX-encoded insns). Subsequently
> _get_fpu() would then assert that CR0.PE must be set (and EFLAGS.VM
> clear) when trying to invoke YMM, ZMM, or OPMASK state.
> 
> Signed-off-by: Jan Beulich <jbeulich@suse.com>

> ---
> v2: Correct placement of new declaration in fuzz_write_segment().
> 
> --- a/tools/fuzz/x86_instruction_emulator/fuzz-emul.c
> +++ b/tools/fuzz/x86_instruction_emulator/fuzz-emul.c
> @@ -76,6 +76,8 @@ static inline bool input_read(struct fuz
>     return true;
> }
> 
> +static bool sanitize_state(struct x86_emulate_ctxt *ctxt);
> +
> static const char* const x86emul_return_string[] = {
>     [X86EMUL_OKAY] = "X86EMUL_OKAY",
>     [X86EMUL_UNHANDLEABLE] = "X86EMUL_UNHANDLEABLE",
> @@ -424,8 +426,19 @@ static int fuzz_write_segment(
>     rc = maybe_fail(ctxt, "write_segment", true);
> 
>     if ( rc == X86EMUL_OKAY )
> +    {
> +        struct segment_register old = c->segments[seg];
> +
>         c->segments[seg] = *reg;
> 
> +        if ( !sanitize_state(ctxt) )
> +        {
> +            c->segments[seg] = old;
> +            x86_emul_hw_exception(13 /* #GP */, 0, ctxt);
> +            rc = X86EMUL_EXCEPTION;
> +        }
> +    }
> +
>     return rc;
> }
> 
> @@ -452,6 +465,7 @@ static int fuzz_write_cr(
> {
>     struct fuzz_state *s = ctxt->data;
>     struct fuzz_corpus *c = s->corpus;
> +    unsigned long old;
>     int rc;
> 
>     if ( reg >= ARRAY_SIZE(c->cr) )
> @@ -461,9 +475,17 @@ static int fuzz_write_cr(
>     if ( rc != X86EMUL_OKAY )
>         return rc;
> 
> +    old = c->cr[reg];
>     c->cr[reg] = val;
> 
> -    return X86EMUL_OKAY;
> +    if ( !sanitize_state(ctxt) )
> +    {
> +        c->cr[reg] = old;
> +        x86_emul_hw_exception(13 /* #GP */, 0, ctxt);
> +        rc = X86EMUL_EXCEPTION;
> +    }
> +
> +    return rc;
> }
> 
> #define fuzz_read_xcr emul_test_read_xcr
> @@ -561,7 +583,16 @@ static int fuzz_write_msr(
>     {
>         if ( msr_index[idx] == reg )
>         {
> +            uint64_t old = c->msr[idx];
> +
>             c->msr[idx] = val;
> +
> +            if ( !sanitize_state(ctxt) )
> +            {
> +                c->msr[idx] = old;
> +                break;
> +            }
> +
>             return X86EMUL_OKAY;
>         }
>     }
> @@ -808,6 +839,30 @@ static void sanitize_input(struct x86_em
>     }
> }
> 
> +/*
> + * Call this function from hooks potentially altering machine state into
> + * something that's not architecturally valid, yet which - as per above -
> + * the emulator relies on.
> + */
> +static bool sanitize_state(struct x86_emulate_ctxt *ctxt)
> +{
> +    const struct fuzz_state *s = ctxt->data;
> +    const struct fuzz_corpus *c = s->corpus;
> +    const struct cpu_user_regs *regs = &c->regs;
> +
> +    if ( long_mode_active(ctxt) && !(c->cr[0] & X86_CR0_PG) )
> +        return false;
> +
> +    if ( (c->cr[0] & X86_CR0_PG) && !(c->cr[0] & X86_CR0_PE) )
> +        return false;
> +
> +    if ( (regs->rflags & X86_EFLAGS_VM) &&
> +         (c->segments[x86_seg_cs].db || c->segments[x86_seg_ss].db) )
> +        return false;
> +
> +    return true;
> +}

Sorry, I didn’t read this function very well on Friday.  It’s not actually doing any sanitation; rather, it’s checking whether the state is architecturally valid.  Or more precisely: it’s checking whether the emulator's assumptions about the state still hold.

check_state?  sanity_check_state?  

 -George
Jan Beulich April 1, 2019, 12:05 p.m. UTC | #2
>>> On 01.04.19 at 12:44, <George.Dunlap@citrix.com> wrote:
>> On Apr 1, 2019, at 8:46 AM, Jan Beulich <JBeulich@suse.com> wrote:
>> +/*
>> + * Call this function from hooks potentially altering machine state into
>> + * something that's not architecturally valid, yet which - as per above -
>> + * the emulator relies on.
>> + */
>> +static bool sanitize_state(struct x86_emulate_ctxt *ctxt)
>> +{
>> +    const struct fuzz_state *s = ctxt->data;
>> +    const struct fuzz_corpus *c = s->corpus;
>> +    const struct cpu_user_regs *regs = &c->regs;
>> +
>> +    if ( long_mode_active(ctxt) && !(c->cr[0] & X86_CR0_PG) )
>> +        return false;
>> +
>> +    if ( (c->cr[0] & X86_CR0_PG) && !(c->cr[0] & X86_CR0_PE) )
>> +        return false;
>> +
>> +    if ( (regs->rflags & X86_EFLAGS_VM) &&
>> +         (c->segments[x86_seg_cs].db || c->segments[x86_seg_ss].db) )
>> +        return false;
>> +
>> +    return true;
>> +}
> 
> Sorry, I didn’t read this function very well on Friday.  It’s not actually 
> doing any sanitation; rather, it’s checking whether the state is 
> architecturally valid.  Or more precisely: it’s checking whether the 
> emulator's assumptions about the state still hold.
> 
> check_state?  sanity_check_state?  

Hmm, yes - initially I was meaning to alter state, and then I decided
differently but didn't change the name. I'll go with check_state().

Jan

Patch
diff mbox series

--- a/tools/fuzz/x86_instruction_emulator/fuzz-emul.c
+++ b/tools/fuzz/x86_instruction_emulator/fuzz-emul.c
@@ -76,6 +76,8 @@  static inline bool input_read(struct fuz
     return true;
 }
 
+static bool sanitize_state(struct x86_emulate_ctxt *ctxt);
+
 static const char* const x86emul_return_string[] = {
     [X86EMUL_OKAY] = "X86EMUL_OKAY",
     [X86EMUL_UNHANDLEABLE] = "X86EMUL_UNHANDLEABLE",
@@ -424,8 +426,19 @@  static int fuzz_write_segment(
     rc = maybe_fail(ctxt, "write_segment", true);
 
     if ( rc == X86EMUL_OKAY )
+    {
+        struct segment_register old = c->segments[seg];
+
         c->segments[seg] = *reg;
 
+        if ( !sanitize_state(ctxt) )
+        {
+            c->segments[seg] = old;
+            x86_emul_hw_exception(13 /* #GP */, 0, ctxt);
+            rc = X86EMUL_EXCEPTION;
+        }
+    }
+
     return rc;
 }
 
@@ -452,6 +465,7 @@  static int fuzz_write_cr(
 {
     struct fuzz_state *s = ctxt->data;
     struct fuzz_corpus *c = s->corpus;
+    unsigned long old;
     int rc;
 
     if ( reg >= ARRAY_SIZE(c->cr) )
@@ -461,9 +475,17 @@  static int fuzz_write_cr(
     if ( rc != X86EMUL_OKAY )
         return rc;
 
+    old = c->cr[reg];
     c->cr[reg] = val;
 
-    return X86EMUL_OKAY;
+    if ( !sanitize_state(ctxt) )
+    {
+        c->cr[reg] = old;
+        x86_emul_hw_exception(13 /* #GP */, 0, ctxt);
+        rc = X86EMUL_EXCEPTION;
+    }
+
+    return rc;
 }
 
 #define fuzz_read_xcr emul_test_read_xcr
@@ -561,7 +583,16 @@  static int fuzz_write_msr(
     {
         if ( msr_index[idx] == reg )
         {
+            uint64_t old = c->msr[idx];
+
             c->msr[idx] = val;
+
+            if ( !sanitize_state(ctxt) )
+            {
+                c->msr[idx] = old;
+                break;
+            }
+
             return X86EMUL_OKAY;
         }
     }
@@ -808,6 +839,30 @@  static void sanitize_input(struct x86_em
     }
 }
 
+/*
+ * Call this function from hooks potentially altering machine state into
+ * something that's not architecturally valid, yet which - as per above -
+ * the emulator relies on.
+ */
+static bool sanitize_state(struct x86_emulate_ctxt *ctxt)
+{
+    const struct fuzz_state *s = ctxt->data;
+    const struct fuzz_corpus *c = s->corpus;
+    const struct cpu_user_regs *regs = &c->regs;
+
+    if ( long_mode_active(ctxt) && !(c->cr[0] & X86_CR0_PG) )
+        return false;
+
+    if ( (c->cr[0] & X86_CR0_PG) && !(c->cr[0] & X86_CR0_PE) )
+        return false;
+
+    if ( (regs->rflags & X86_EFLAGS_VM) &&
+         (c->segments[x86_seg_cs].db || c->segments[x86_seg_ss].db) )
+        return false;
+
+    return true;
+}
+
 int LLVMFuzzerInitialize(int *argc, char ***argv)
 {
     if ( !emul_test_init() )