GaloisInc / saw-script

The SAW scripting language.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SAW MIR override with slice argument fails to match

RyanGlScott opened this issue · comments

Here are two Rust functions, both of which take a slice argument:

// test.rs
pub fn f(s: &[u8]) -> u8 {
    s[0] + s[1]
}

pub fn g(s: &[u8]) -> u8 {
    f(s)
}

g does nothing but call f, so I would expect that the following compositional specification for g would verify:

// test.saw
enable_experimental;

let oneThroughFive = mir_term {{ [1, 2, 3, 4, 5] : [5][8] }};

let f_spec = do {
  a <- mir_alloc (mir_array 5 mir_u8);
  mir_points_to a oneThroughFive;

  mir_execute_func [mir_slice_value a];

  mir_return (mir_term {{ 3 : [8] }});
};

m <- mir_load_module "test.linked-mir.json";

f_ov <- mir_verify m "test::f" [] false f_spec z3;
mir_verify m "test::g" [f_ov] false f_spec z3;

While f does indeed verify against f_spec, g does not verify, much to my surprise:

$ ~/Software/saw-1.1/bin/saw test.saw
[13:23:34.144] Loading file "/home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw"
[13:23:34.209] Verifying test/8d8066da::f[0] ...
[13:23:34.224] Simulating test/8d8066da::f[0] ...
[13:23:34.225] Checking proof obligations test/8d8066da::f[0] ...
[13:23:34.241] Proof succeeded! test/8d8066da::f[0]
[13:23:34.280] Verifying test/8d8066da::g[0] ...
[13:23:34.294] Simulating test/8d8066da::g[0] ...
[13:23:34.295] Matching 1 overrides of  test/8d8066da::f[0] ...
[13:23:34.295] Stack trace:
"mir_verify" (/home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw:18:1-18:11)
Symbolic execution failed.
Abort due to assertion failure:
  test.rs:6:7: 6:8: error: in test/8d8066da::g[0]
  All overrides failed during structural matching:
  *  Name: test/8d8066da::f[0]
     Location: /home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw:17:9
     Argument types: 
     - &[u8]
     Return type: u8
     Arguments:
     - <slice>
     at /home/ryanscott/Documents/Hacking/Haskell/saw-script/test.saw:8:3
     could not match specified value with actual value:
       actual (simulator) value: 0x1:[8]
       specified value:          let { x@1 = Prelude.Vec 8 Prelude.Bool
                                       x@2 = Cryptol.PLiteralSeqBool
                                               (Cryptol.TCNum 8)
           }
        in [ Cryptol.ecNumber (Cryptol.TCNum 1) x@1 x@2
       , Cryptol.ecNumber (Cryptol.TCNum 2) x@1 x@2
       , Cryptol.ecNumber (Cryptol.TCNum 3) x@1 x@2
       , Cryptol.ecNumber (Cryptol.TCNum 4) x@1 x@2
       , Cryptol.ecNumber (Cryptol.TCNum 5) x@1 x@2 ] : [5][8]
       type of actual value:    u8
       type of specified value: [u8; 5]

The reason that SAW gives is rather bewildering, as the actual and specified values aren't even of the same type.

Here is what is happening when you run this program:

  1. When g attempts to match the f override, it first matches the mir_slice_value a argument in the specification against the actual slice value that g passes to f in the simulator. The code that powers this lives here. This code ensures that (1) the lengths of the two slices are the same, and (2) the slice's underlying references match.

  2. The "slice's underlying references match" is the interesting part. In the spec, the underlying reference is derived from a, which is allocated at type &[u8; 5]. However, when a is passed to mir_slice_value, we use crucible-mir's indexing operations to turn it into something of type *const u8. This is because Rust actually does represent slices with pointers in this way, and crucible-mir models this slice representation faithfully.

    This means during matching, SAW "assigns" a to be a reference to something of type u8, not a reference to something of type [u8; 5]. This is ultimately the root cause of the bug, and things will go awry after this point.

  3. After matching the mir_slice_value a argument, SAW will process the mir_points_to a oneThroughFive statement in the override for f_spec to assert that a does in fact point to oneThroughFive before applying the override. The code that powers this lives here. This works by (1) reading the value that a points to, based on the type of a, and (2) checking that the value is equal to oneThroughFive.

    However, recall that we assigned a to be a reference to something of type u8 earlier. This means that the value we read from a will be 1, _not [1, 2, 3, 4, 5]! This is a problem, and it's the reason why the error message has the type mismatch.

I'm not entirely sure what the best way to resolve this would be. Some possibilities:

  • Register the underlying reference in mir_slice_value a at type [u8; 5], not u8. This is a bit suspicious, however, given that it really does have the latter type, and I suspect that crucible-mir might even error if you tried to perform the load at type [u8; 5] later.
  • Have a special case for slice-derived references in step (3) above. That is, if the reference is derived from a slice, don't load a single value, but instead load multiple values corresponding to the length of the slice. This would require that we augment MirPointer with information about whether a pointer is associated with a slice or not.

Happily, there is a third, much more direct possibility:

  • Recover the a from the expression mir_slice_value a and register that at type [u8; 5].

I originally thought this form of recovery not to be possible, but thankfully, crucible-mir offers a way to do this using a lesser-known memory model operation (mirRef_peelIndexIO) that I wasn't aware of until now. I'll submit a PR using this approach.