Skip to content
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

Spurious failure on harness from s2n-quic #2650

Closed
zhassan-aws opened this issue Aug 1, 2023 · 5 comments · Fixed by #2796
Closed

Spurious failure on harness from s2n-quic #2650

zhassan-aws opened this issue Aug 1, 2023 · 5 comments · Fixed by #2796
Assignees
Labels
[C] Bug This is a bug. Something isn't working. [F] Spurious Failure Issues that cause Kani verification to fail despite the code being correct. T-CBMC Issue related to an existing CBMC issue T-High Priority Tag issues that have high priority T-User Tag user issues / requests

Comments

@zhassan-aws
Copy link
Contributor

zhassan-aws commented Aug 1, 2023

This bug was reported by @camshaft on s2n-quic. I've extracted a stand-alone example for easy debugging:

use std::ops::{Deref, DerefMut};

//
// from = [[aaa][bbbbb][cc][ddddd]]
// to   = [[xx][xxx][xxxxx][xxxxxxxxx]]
//
// result:
// to'  = [[aa][abb][bbbcc][ddddddxxx]]
//
pub fn vectored_copy<A, B>(from: &[A], to: &mut [B])
where
    A: Deref<Target = [u8]>,
    B: Deref<Target = [u8]> + DerefMut,
{
    let mut from_index = 0;
    let mut from_offset = 0;

    let mut to_index = 0;
    let mut to_offset = 0;

    // iterate until we reach one of the ends
    while from_index < from.len() && to_index < to.len() {
        let from = unsafe {
            // Safety: this length is already checked in the while condition
            debug_assert!(from.len() > from_index);
            from.get_unchecked(from_index)
        };

        let to = unsafe {
            // Safety: this length is already checked in the while condition
            debug_assert!(to.len() > to_index);
            to.get_unchecked_mut(to_index)
        };

        {
            // copy the bytes in the current views
            let from = unsafe {
                // Safety: the slice offsets are checked at the end of the while loop
                debug_assert!(from.len() >= from_offset);
                from.get_unchecked(from_offset..)
            };

            let to = unsafe {
                // Safety: the slice offsets are checked at the end of the while loop
                debug_assert!(to.len() >= to_offset);
                to.get_unchecked_mut(to_offset..)
            };

            let len = from.len().min(to.len());

            unsafe {
                // Safety: by using the min of the two lengths we will never exceed
                //         either slice's buffer
                debug_assert!(from.len() >= len);
                debug_assert!(to.len() >= len);
                to.get_unchecked_mut(..len)
                    .copy_from_slice(from.get_unchecked(..len));
            }

            // increment the offsets
            from_offset += len;
            to_offset += len;
        }

        // check if the `from` is done
        if from.len() == from_offset {
            from_index += 1;
            from_offset = 0;
        }

        // check if the `to` is done
        if to.len() == to_offset {
            to_index += 1;
            to_offset = 0;
        }
    }
}

struct F {
    values: [u8; 2],
    len: usize,
}

impl std::ops::Deref for F {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        &self.values[..self.len]
    }
}

impl std::ops::DerefMut for F {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.values[..self.len]
    }
}

struct G {
    values: [F; 2],
    len: usize,
}

impl std::ops::Deref for G {
    type Target = [F];

    fn deref(&self) -> &Self::Target {
        &self.values[..self.len]
    }
}

impl std::ops::DerefMut for G {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.values[..self.len]
    }
}


#[kani::proof]
#[kani::unwind(5)]
fn main() {
    let ndlen = kani::any_where(|l| *l <= 2);
    let from = G { values: [F { values: [255, 255], len: ndlen }, F { values: [255, 255], len: 2}], len: 2};
    let mut to = G { values: [F { values: [255, 255], len: 1 }, F { values: [255, 255], len: 1}], len: 2};
    vectored_copy(&from, &mut to);
}

using the following command line invocation:

cargo kani

with Kani version: a2a1e85

I expected to see this happen: Verification successful

Instead, this happened: Verification failed:

Check 16: core::slice::index::slice_end_index_len_fail_rt.assertion.1
         - Status: FAILURE
         - Description: "This is a placeholder message; Kani doesn't support message formatted at runtime"
         - Location: ../../.rustup/toolchains/nightly-2023-07-01-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:76:5 in function core::slice::index::slice_end_index_len_fail_rt

Running concrete playback using the following command:

$ cargo kani --enable-unstable --concrete-playback=inplace

generates the following test:

#[test]
fn kani_concrete_playback_main_11688673437834003895() {
    let concrete_vals: Vec<Vec<u8>> = vec![
        // 2ul
        vec![2, 0, 0, 0, 0, 0, 0, 0],
    ];
    kani::concrete_playback_run(concrete_vals, main);
}

If I run this test, it doesn't fail:

$ cargo kani playback -Zconcrete-playback 
Kani Rust Verifier 0.33.0 (cargo plugin)
   Compiling deref v0.1.0 (/home/ubuntu/examples/deref)
    Finished test [unoptimized + debuginfo] target(s) in 0.65s
     Running unittests src/main.rs (target/x86_64-unknown-linux-gnu/debug/deps/deref-f52e207434ba7027)

running 1 test
test kani_concrete_playback_main_11688673437834003895 ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Also, if I replace ndlen in the harness with 2 as the playback test suggests and run Kani, verification succeeds:

SUMMARY:
 ** 0 of 96 failed (4 unreachable)

VERIFICATION:- SUCCESSFUL
Verification Time: 0.68793035s

Complete - 1 successfully verified harnesses, 0 failures, 1 total.
@zhassan-aws zhassan-aws added [C] Bug This is a bug. Something isn't working. T-High Priority Tag issues that have high priority [F] Spurious Failure Issues that cause Kani verification to fail despite the code being correct. T-User Tag user issues / requests labels Aug 1, 2023
@remi-delmas-3000
Copy link
Contributor

remi-delmas-3000 commented Aug 3, 2023

Hi, I went as far back as kani 0.17.0 and cbmc 5.72.0 and the problem persists so this does not seem new.

Looking at the trace, it appears that the self.len for is not constrained as expected for the result of to.get_unchecked_mut(to_index).

pub struct F {
    values: [u8; 2],
    len: usize,
}

impl std::ops::Deref for F {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        &self.values[..self.len]
    }
}

impl std::ops::DerefMut for F {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.values[..self.len]
    }
}

pub struct G {
    values: [F; 2],
    len: usize,
}

impl std::ops::Deref for G {
    type Target = [F];

    fn deref(&self) -> &Self::Target {
        &self.values[..self.len]
    }
}

impl std::ops::DerefMut for G {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.values[..self.len]
    }
}

//
// from = [[aaa][bbbbb][cc][ddddd]]
// to   = [[xx][xxx][xxxxx][xxxxxxxxx]]
//
// result:
// to'  = [[aa][abb][bbbcc][ddddddxxx]]
//
pub fn vectored_copy(from: &[F], to: &mut [F])
{
    let mut from_index = 0;
    let mut from_offset = 0;

    let mut to_index = 0;
    let mut to_offset = 0;

    // iterate until we reach one of the ends
    while from_index < from.len() && to_index < to.len() {
        let f_from: &F = unsafe {
            // Safety: this length is already checked in the while condition
            debug_assert!(from.len() > from_index);
            from.get_unchecked(from_index)
        };

        let f_to: &mut F = unsafe {
            // Safety: this length is already checked in the while condition
            debug_assert!(to.len() > to_index);
            to.get_unchecked_mut(to_index)
        };
        // fails here with
        let _dummy_from_index = from_index; // 1
        let _dummy_from_offset = from_offset; // 0
        let _dummy_to_index = to_index; // 1
        let _dummy_to_offset = to_offset; // 0
        let _dummy_to_len = f_to.len; // 18374686479671623680ul
        // this should hold but does not because
        // the output of to.get_unchecked_mut(to_index) is unconstrained
        assert!(f_to.len <= 2); 

        // this fixes the problem downstream
        kani::assume(f_to.len <= 2);
        {
            // copy the bytes in the current views
            let u8_from: &[u8] = unsafe {
                // Safety: the slice offsets are checked at the end of the while loop
                debug_assert!(f_from.len() >= from_offset);
                f_from.get_unchecked(from_offset..)
            };

            let u8_to: &mut [u8] = unsafe {
                // Safety: the slice offsets are checked at the end of the while loop
                debug_assert!(f_to.len() >= to_offset);
                f_to.get_unchecked_mut(to_offset..)
            };

            let len = u8_from.len().min(u8_to.len());

            unsafe {
                // Safety: by using the min of the two lengths we will never exceed
                //         either slice's buffer
                debug_assert!(u8_from.len() >= len);
                debug_assert!(u8_to.len() >= len);
                u8_to.get_unchecked_mut(..len)
                    .copy_from_slice(u8_from.get_unchecked(..len));
            }

            // increment the offsets
            from_offset += len;
            to_offset += len;
        }

        // check if the `from` is done
        if f_from.len() == from_offset {
            from_index += 1;
            from_offset = 0;
        }

        // check if the `to` is done
        if f_to.len() == to_offset {
            to_index += 1;
            to_offset = 0;
        }
    }
}


#[kani::proof]
#[kani::unwind(5)]
fn main() {
    let ndlen = kani::any_where(|l| *l <= 2);
    let from = G { values: [F { values: [255, 255], len: ndlen }, F { values: [255, 255], len: 2}], len: 2};
    let mut to = G { values: [F { values: [255, 255], len: 1 }, F { values: [255, 255], len: 1}], len: 2};
    vectored_copy(&from, &mut to);
}

Looking at the --show-vcc output of CBMC, it seems like the variable encoding the return value of f_to.get_unchecked_mut(to_index) unconstrained, instead of being equal to the actual expression that defines the return value of the function.

This is the variable representing f_to,

_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_36::f_to!0@1#3

This is the variable representing the return value of to.get_unchecked_mut(to_index)

goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2

The only constraint we have for it is of the form x = x

{-4799} goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2 = goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2

The variable that actually defines the return value of the function is this one:

 // Labels: bb4
     5: SET RETURN VALUE _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_0

In the SSA it shows up as:

{-4798} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_0!0@2#2 = goto_symex::return_value::_RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_!0#3

So equation -4799 should have been this instead:

{-4799} goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2 = _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_0!0@2#2

Here is the relevant --show-vcc extract

// body of  function _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_
{-4782} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_1::self!0@2#1 = _RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_::1::var_2::index!0@2#1
{-4783} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_2::slice!0@2#1..data = cast(address_of(main::1::var_12::to!0@1), struct tag-F*)
{-4784} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_2::slice!0@2#1..len = 2
{-4785} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_3!0@2#2 = 0
{-4786} _RNvMs_NtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrOSNtCshFivlfURAdq_18s2n_quick_spurious1F10as_mut_ptrBH_::1::var_1::self!0@3#1..data = cast(address_of(main::1::var_12::to!0@1), struct tag-F*)
{-4787} _RNvMs_NtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrOSNtCshFivlfURAdq_18s2n_quick_spurious1F10as_mut_ptrBH_::1::var_1::self!0@3#1..len = 2
{-4788} _RNvMs_NtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrOSNtCshFivlfURAdq_18s2n_quick_spurious1F10as_mut_ptrBH_::1::var_0!0@3#2 = cast(address_of(main::1::var_12::to!0@1), struct tag-F*)
{-4789} goto_symex::return_value::_RNvMs_NtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrOSNtCshFivlfURAdq_18s2n_quick_spurious1F10as_mut_ptrBH_!0#3 = cast(address_of(main::1::var_12::to!0@1), struct tag-F*)
{-4790} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_6!0@2#2 = cast(address_of(main::1::var_12::to!0@1), struct tag-F*)
{-4791} _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_1::self!0@3#1 = cast(address_of(main::1::var_12::to!0@1), struct tag-F*)
{-4792} _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_2::count!0@3#1 = _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_1::self!0@2#1
{-4793} ¬goto_symex::\guard#15 ⇒ ¬(overflow-*(cast(_RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_2::count!0@3#1, signedbv[64]), 8))
{-4794} ¬goto_symex::\guard#15 ⇒ ¬(overflow-+(cast(cast(address_of(main::1::var_12::to!0@1), unsignedbv[64]), signedbv[64]), cast(_RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_2::count!0@3#1, signedbv[64]) * 8))
{-4795} _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::$tmp::tmp_statement_expression!0@3#2 = cast(address_of(main::1::var_12::to!0@1), struct tag-F*) + _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_2::count!0@3#1
{-4796} _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_0!0@3#2 = _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::$tmp::tmp_statement_expression!0@3#2
{-4797} goto_symex::return_value::_RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_!0#3 = _RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_::1::var_0!0@3#2


/// the local defining the return value
{-4798} _RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_::1::var_0!0@2#2 = goto_symex::return_value::_RNvMNtNtCsdR6DzVNQbQk_4core3ptr7mut_ptrONtCshFivlfURAdq_18s2n_quick_spurious1F3addBE_!0#3

/// the incorrect return value equation
{-4799} goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2 = goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2


/// the return value is unconstrained and propagates through this:
{-4800} _RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_::1::var_3!0@2#2 = goto_symex::return_value::_RNvXs0_NtNtCsdR6DzVNQbQk_4core5slice5indexjINtB5_10SliceIndexSNtCshFivlfURAdq_18s2n_quick_spurious1FE17get_unchecked_mutB10_!0#2
{-4801} _RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_::1::var_0!0@2#2 = _RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_::1::var_3!0@2#2
{-4802} goto_symex::return_value::_RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_!0#2 = _RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_::1::var_0!0@2#2
{-4803} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_37!0@1#3 = goto_symex::return_value::_RINvMNtCsdR6DzVNQbQk_4core5sliceSNtCshFivlfURAdq_18s2n_quick_spurious1F17get_unchecked_mutjEBx_!0#2
{-4804} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_36::f_to!0@1#3 = _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_37!0@1#3
{-4805} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_39::_dummy_from_index!0@1#3 = _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_3::from_index!0@1#4
{-4806} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_40::_dummy_from_offset!0@1#3 = _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_4::from_offset!0@1#5
{-4807} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_41::_dummy_to_index!0@1#3 = _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_5::to_index!0@1#4
{-4808} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_42::_dummy_to_offset!0@1#3 = _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_6::to_offset!0@1#5
{-4809} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_43::_dummy_f_to_len!0@1#3 = byte_extract_little_endian({ { { { main::1::var_12::to!0@1#5..values[[0]]..values[[0]], main::1::var_12::to!0@1#5..values[[0]]..values[[1]] }, main::1::var_12::to!0@1#5..values[[0]]..$pad1, main::1::var_12::to!0@1#5..values[[0]]..len }, { { main::1::var_12::to!0@1#5..values[[1]]..values[[0]], main::1::var_12::to!0@1#5..values[[1]]..values[[1]] }, main::1::var_12::to!0@1#5..values[[1]]..$pad1, main::1::var_12::to!0@1#5..values[[1]]..len } }, main::1::var_12::to!0@1#5..len }, pointer_offset(_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_36::f_to!0@1#3) + 8, unsignedbv[64])

// this byte extract models the `f_to.len` field access. `f_to` is represented by the  pointer
// _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_36::f_to!0@1#3
// which points into the struct `to` of type "slice of G"
{-4810} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_48!0@1#3 = byte_extract_little_endian(
// the struct of type G reassembled from its components for the byte_exctract 
    {
        {
            { { main::1::var_12::to!0@1#5..values[[0]]..values[[0]], main::1::var_12::to!0@1#5..values[[0]]..values[[1]] }, main::1::var_12::to!0@1#5..values[[0]]..$pad1, main::1::var_12::to!0@1#5..values[[0]]..len },
            { { main::1::var_12::to!0@1#5..values[[1]]..values[[0]], main::1::var_12::to!0@1#5..values[[1]]..values[[1]] }, main::1::var_12::to!0@1#5..values[[1]]..$pad1, main::1::var_12::to!0@1#5..values[[1]]..len }
        },
        // len of the array
        main::1::var_12::to!0@1#5..len
    }, pointer_offset(_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_36::f_to!0@1#3) + 8 /* this must be the offset of the len field in an F struct */, unsignedbv[64]
)

/// the failed proof obligation
{-4811} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_47!0@1#3 = (_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_48!0@1#3 ≥ 3 ? 0 : 1)
{-4812} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_46!0@1#3 = (_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_47!0@1#3 = 0 ? 1 : 0)
{-4813} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_45!0@1#3 = (_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_46!0@1#3 = 0 ? 1 : 0)
{-4814} _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::temp_3!0@2#2 ⇔ (_RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::var_45!0@1#3 ≠ 0)
├──────────────────────────
{1} ¬goto_symex::\guard#15 ⇒ _RNvCshFivlfURAdq_18s2n_quick_spurious13vectored_copy::1::temp_3!0@2#2

This looks like a CBMC bug,

@tautschnig can you take a second look ?

@feliperodri feliperodri added the T-CBMC Issue related to an existing CBMC issue label Aug 7, 2023
@zhassan-aws zhassan-aws self-assigned this Aug 8, 2023
@zhassan-aws
Copy link
Contributor Author

A slightly reduced version:

use std::ops::{Deref, DerefMut};

pub fn vectored_copy<B>(from: &[&[u8]], to: &mut [B])
where
    B: Deref<Target = [u8]> + DerefMut,
{
    let mut from_index = 0;
    let mut from_offset = 0;

    let mut to_index = 0;
    let mut to_offset = 0;

    // iterate until we reach one of the ends
    while from_index < from.len() && to_index < to.len() {
        let from = unsafe {
            from.get_unchecked(from_index)
        };

        let to = unsafe {
            to.get_unchecked_mut(to_index)
        };

        {
            // copy the bytes in the current views
            let from = unsafe {
                from.get_unchecked(from_offset..)
            };

            let to = unsafe {
                to.get_unchecked_mut(to_offset..)
            };

            let len = from.len().min(to.len());

            unsafe {
                to.get_unchecked_mut(..len)
                    .copy_from_slice(from.get_unchecked(..len));
            }

            // increment the offsets
            from_offset += len;
            to_offset += len;
        }

        // check if the `from` is done
        if from.len() == from_offset {
            from_index += 1;
            from_offset = 0;
        }

        // check if the `to` is done
        if to.len() == to_offset {
            to_index += 1;
            to_offset = 0;
        }
    }
}

struct F {
    values: [u8; 2],
    len: usize,
}

impl std::ops::Deref for F {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        &self.values[..self.len]
    }
}

impl std::ops::DerefMut for F {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.values[..self.len]
    }
}

struct G {
    values: [F; 2],
    _len: usize,
}

impl std::ops::Deref for G {
    type Target = [F];

    fn deref(&self) -> &Self::Target {
        &self.values
    }
}

impl std::ops::DerefMut for G {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.values
    }
}


#[kani::proof]
#[kani::unwind(5)]
fn main() {
    let ndlen = kani::any_where(|l| *l == 2);
    let from1 = [255, 255];
    let from2 = [255, 255];
    let from = [&from1[..ndlen], &from2[..]];
    let mut to = G { values: [F { values: [255, 255], len: 1 }, F { values: [255, 255], len: 1}], _len: 2};
    vectored_copy(&from, &mut to);
}

@zhassan-aws zhassan-aws assigned tautschnig and unassigned zhassan-aws Aug 8, 2023
@remi-delmas-3000
Copy link
Contributor

@tautschnig full reproducer posted above

@tautschnig
Copy link
Member

This is caused by a bug in CBMC's handling of byte updates of non-constant width (which are generated from to.get_unchecked_mut(..len).copy_from_slice(from.get_unchecked(..len));). Fixed once diffblue/cbmc#7855 is merged and part of a release.

@zhassan-aws
Copy link
Contributor Author

diffblue/cbmc#7855 was merged. This issue should get fixed once we get a new CBMC release.

@zhassan-aws zhassan-aws mentioned this issue Sep 29, 2023
4 tasks
zhassan-aws added a commit that referenced this issue Sep 29, 2023
Upgrade CBMC version to latest (5.93.0).

Resolves #2650
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[C] Bug This is a bug. Something isn't working. [F] Spurious Failure Issues that cause Kani verification to fail despite the code being correct. T-CBMC Issue related to an existing CBMC issue T-High Priority Tag issues that have high priority T-User Tag user issues / requests
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants