-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Panic with enable_lax_loads_and_stores
and nested structs
#1684
Comments
I spent some time looking into this, and the culprit appears to be the way SMT-array-backed memory treats structs with padding. In particular, The first two bytes correspond to the Struct sflds -> assert (not (null r)) $ snd $ foldl1 cv r
where cv (wx,x) (wy,y) = (wx+wy, concatBV wx x wy y)
r = concat (zipWith val [0..] (V.toList sflds))
val i f = v1
where eo = so + fieldOffset f
ee = eo + storageTypeSize (f^.fieldVal)
v1 | le <= eo = v2
| ee <= lo = v2
| otherwise = retValue eo (FieldVal sflds i v) : v2
v2 | fieldPad f == 0 = [] -- Return no padding.
| le <= ee = [] -- Nothing of load ends before padding.
-- Nothing if padding ends before load begins.
| ee+fieldPad f <= lo = []
| otherwise = [(p, ValueCtorVar badMem)]
where p = min (ee+fieldPad f) le - (max lo ee)
tpPad = bitvectorType p
badMem = InvalidMemory tpPad If you squint, you'll see that attempting to load struct padding results in InvalidMemory tp'-> Partial.partErr sym mop $ Invalid tp' And converted to a panic here: Partial.Err _ ->
panic "wrietMemWithAllocationCheck"
[ "Expected succesful byte load when updating SMT array"
, "but got an error instead"
] The question to answer, then, is what should this do? Given that the semantics of |
I think the intent of "lax loads" is basically to make it so that invalid memory exceptions don't occur, so I think skipping that check when it is enabled feels like the right thing to do. We should probably also downgrade that |
I tried the following naïve fix in diff --git a/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Common.hs b/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Common.hs
index 66e2de872..3cdadd993 100644
--- a/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Common.hs
+++ b/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Common.hs
@@ -496,7 +496,10 @@ loadBitvector lo lw so v = do
v1 | le <= eo = v2
| ee <= lo = v2
| otherwise = retValue eo (FieldVal sflds i v) : v2
- v2 | fieldPad f == 0 = [] -- Return no padding.
+ v2 | laxLoadsAndStores ?memOpts
+ , indeterminateLoadBehavior ?memOpts == StableSymbolic
+ = []
+ | fieldPad f == 0 = [] -- Return no padding.
| le <= ee = [] -- Nothing of load ends before padding.
-- Nothing if padding ends before load begins.
| ee+fieldPad f <= lo = [] Unfortunately, that just changes the panic to... this:
I'm presuming that the |
After reading the implementation of diff --git a/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Generic.hs b/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Generic.hs
index 2a7e3a8b..74db4795 100644
--- a/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Generic.hs
+++ b/crucible-llvm/src/Lang/Crucible/LLVM/MemModel/Generic.hs
@@ -1259,15 +1259,21 @@ writeMemWithAllocationCheck is_allocated sym w gsym ptr tp alignment val mem = d
, case val of
LLVMValInt block _ -> (asNat block) == (Just 0)
_ -> True -> do
- let subFn :: ValueLoad Addr -> IO (PartLLVMVal sym)
+ let subFn :: ValueLoad Addr -> IO (Maybe (PartLLVMVal sym))
subFn = \case
- LastStore val_view -> applyView
+ LastStore val_view -> fmap Just $ applyView
sym
(memEndianForm mem)
mop
(Partial.totalLLVMVal sym val)
val_view
- InvalidMemory tp'-> Partial.partErr sym mop $ Invalid tp'
+ InvalidMemory tp'
+ | laxLoadsAndStores ?memOpts
+ , indeterminateLoadBehavior ?memOpts == StableSymbolic
+ -> pure Nothing
+
+ | otherwise
+ -> fmap Just $ Partial.partErr sym mop $ Invalid tp'
OldMemory off _ -> panic "Generic.writeMemWithAllocationCheck"
[ "Unexpected offset in storage type"
, "*** Offset: " ++ show off
@@ -1279,32 +1285,39 @@ writeMemWithAllocationCheck is_allocated sym w gsym ptr tp alignment val mem = d
Offset ->
IO (SymArray sym (SingleCtx (BaseBVType w)) (BaseBVType 8))
storeArrayByteFn acc_arr off = do
- partial_byte <- genValueCtor sym (memEndianForm mem) mop
- =<< traverse subFn (loadBitvector off 1 0 (ValueViewVar tp))
-
- case partial_byte of
- Partial.NoErr _ (LLVMValInt _ byte)
- | Just Refl <- testEquality (knownNat @8) (bvWidth byte) -> do
- idx <- bvAdd sym (llvmPointerOffset ptr)
- =<< bvLit sym w (bytesToBV w off)
- arrayUpdate sym acc_arr (Ctx.singleton idx) byte
-
- Partial.NoErr _ (LLVMValZero _) -> do
- byte <- bvLit sym knownRepr (BV.zero knownRepr)
- idx <- bvAdd sym (llvmPointerOffset ptr)
- =<< bvLit sym w (bytesToBV w off)
- arrayUpdate sym acc_arr (Ctx.singleton idx) byte
-
- Partial.NoErr _ v ->
- panic "wrietMemWithAllocationCheck"
- [ "Expected byte value when updating SMT array, but got:"
- , show v
- ]
- Partial.Err _ ->
- panic "wrietMemWithAllocationCheck"
- [ "Expected succesful byte load when updating SMT array"
- , "but got an error instead"
- ]
+ ctor_mb <- traverse subFn (loadBitvector off 1 0 (ValueViewVar tp))
+ let mb_ctor = sequenceA ctor_mb
+ mb_partial_byte <- traverse (genValueCtor sym (memEndianForm mem) mop)
+ mb_ctor
+
+ case mb_partial_byte of
+ Nothing -> pure acc_arr
+ Just partial_byte ->
+ case partial_byte of
+ Partial.NoErr _ (LLVMValInt _ byte)
+ | Just Refl <- testEquality (knownNat @8) (bvWidth byte) -> do
+ idx <- bvAdd sym (llvmPointerOffset ptr)
+ =<< bvLit sym w (bytesToBV w off)
+ arrayUpdate sym acc_arr (Ctx.singleton idx) byte
+
+ Partial.NoErr _ (LLVMValZero _) -> do
+ byte <- bvLit sym knownRepr (BV.zero knownRepr)
+ idx <- bvAdd sym (llvmPointerOffset ptr)
+ =<< bvLit sym w (bytesToBV w off)
+ arrayUpdate sym acc_arr (Ctx.singleton idx) byte
+
+ Partial.NoErr _ v ->
+ panic "writeMemWithAllocationCheck"
+ [ "Expected byte value when updating SMT array, but got:"
+ , show v
+ ]
+ Partial.Err _ ->
+ panic "writeMemWithAllocationCheck"
+ [ "Expected succesful byte load when updating SMT array"
+ , "but got an error instead"
+ ]
res_arr <- foldM storeArrayByteFn arr [0 .. (sz - 1)]
overwriteArrayMem sym w ptr res_arr arr_sz mem The key idea is that when we load a byte from a struct that corresponds to struct padding, we skip updating the corresponding byte in the |
When `laxLoadsAndStores` + `stable-symbolic` are enabled, all allocations are backed by fresh SMT arrays. When writing a struct to one of these arrays, we traverse through the array byte-by-byte, loading each byte in the struct and updating the array's value accordingly. Things went awry in the case where the struct has padding, however, as the code in `crucible-llvm` assumed that loading struct padding should always be an error. Not only is this possible with `stable-symbolic`, it has a sensible interpretation: whenever loading a byte of struct padding, just skip updating the array value corresponding to that byte. See GaloisInc/saw-script#1684 for the motivation behind this bugfix. I haven't found a way to trigger the same bug with `crux-llvm`, but I can trigger it programatically using the `crucible-llvm` API. I've added a test case to the `crucible-llvm` test suite to ensure that the bug remains fixed.
When `laxLoadsAndStores` + `stable-symbolic` are enabled, all allocations are backed by fresh SMT arrays. When writing a struct to one of these arrays, we traverse through the array byte-by-byte, loading each byte in the struct and updating the array's value accordingly. Things went awry in the case where the struct has padding, however, as the code in `crucible-llvm` assumed that loading struct padding should always be an error. Not only is this possible with `stable-symbolic`, it has a sensible interpretation: whenever loading a byte of struct padding, just skip updating the array value corresponding to that byte. See GaloisInc/saw-script#1684 for the motivation behind this bugfix. I haven't found a way to trigger the same bug with `crux-llvm`, but I can trigger it programatically using the `crucible-llvm` API. I've added a test case to the `crucible-llvm` test suite to ensure that the bug remains fixed.
When `laxLoadsAndStores` + `stable-symbolic` are enabled, all allocations are backed by fresh SMT arrays. When writing a struct to one of these arrays, we traverse through the array byte-by-byte, loading each byte in the struct and updating the array's value accordingly. Things went awry in the case where the struct has padding, however, as the code in `crucible-llvm` assumed that loading struct padding should always be an error. Not only is this possible with `stable-symbolic`, it has a sensible interpretation: whenever loading a byte of struct padding, just skip updating the array value corresponding to that byte. See GaloisInc/saw-script#1684 for the motivation behind this bugfix. I haven't found a way to trigger the same bug with `crux-llvm`, but I can trigger it programatically using the `crucible-llvm` API. I've added a test case to the `crucible-llvm` test suite to ensure that the bug remains fixed.
When `laxLoadsAndStores` + `stable-symbolic` are enabled, all allocations are backed by fresh SMT arrays. When writing a struct to one of these arrays, we traverse through the array byte-by-byte, loading each byte in the struct and updating the array's value accordingly. Things went awry in the case where the struct has padding, however, as the code in `crucible-llvm` assumed that loading struct padding should always be an error. Not only is this possible with `stable-symbolic`, it has a sensible interpretation: whenever loading a byte of struct padding, just skip updating the array value corresponding to that byte. See GaloisInc/saw-script#1684 for the motivation behind this bugfix. I haven't found a way to trigger the same bug with `crux-llvm`, but I can trigger it programatically using the `crucible-llvm` API. I've added a test case to the `crucible-llvm` test suite to ensure that the bug remains fixed.
Now that the `crucible-llvm` memory model can reason about struct padding with `lax-loads-and-stores` enabled, bumping the `crucible` submodule to bring in that change fixes #1684 as a consequence. I have also added a regression test.
Now that the `crucible-llvm` memory model can reason about struct padding with `lax-loads-and-stores` enabled, bumping the `crucible` submodule to bring in that change fixes #1684 as a consequence. I have also added a regression test.
Fix #1684 by bringing in GaloisInc/crucible#998
Given the following example, which requires a sufficiently recent nightly version of SAW:
SAW will panic when the example is compiled and run as follows:
Note that
enable_lax_loads_and_stores
is crucial to triggering this bug, as if it is commented out, then the proof will succeed.The text was updated successfully, but these errors were encountered: