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

Add support for non-deterministic pointer #2300

Open
zpzigi754 opened this issue Mar 17, 2023 · 11 comments
Open

Add support for non-deterministic pointer #2300

zpzigi754 opened this issue Mar 17, 2023 · 11 comments
Assignees
Labels
[C] Feature / Enhancement A new feature request or enhancement to an existing feature.

Comments

@zpzigi754
Copy link

zpzigi754 commented Mar 17, 2023

Requested feature: assuming the validity of pointers
Use case: system verification

Let's say that an object is allocated in one place (place A) and its pointer is passed through an external interface (e.g., user-kernel boundary) to an another place (place B). In this case where place B cannot keep track of place A's objects, how can place B assume that the passed pointer is valid?

If I try to use the passed pointer directly, it generates a series of dereference failure such as

Failed Checks: dereference failure: pointer NULL
Failed Checks: dereference failure: pointer invalid
Failed Checks: dereference failure: deallocated dynamic object
Failed Checks: dereference failure: dead object
Failed Checks: dereference failure: pointer outside object bounds
Failed Checks: dereference failure: invalid integer address

If I treat the passed pointer as dynamic object with __CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(p));,
one failure still remains.

Failed Checks: dereference failure: pointer outside object bounds

How could I remove that remaining failure?

Test case:

# main.rs
pub struct Context {
    pub len: u32,
}

extern "C" {
    fn ffi_CPROVER_DYNAMIC_OBJECT(p: usize);
}

fn entry(a0: usize) -> Result<usize, ()> {
    unsafe { ffi_CPROVER_DYNAMIC_OBJECT(a0) };     // without this, more pointer-related errors would be generated

    let ctx = a0 as *mut Context;
    unsafe {
        if (*ctx).len > 10 {                // this is the violating condition
            return Err(());
        }
    }
    Ok((0))
}

#[kani::proof]
#[kani::unwind(64)]
fn main() {
    let r0 = kani::any();      // let's say that this value is passed from an external interface (e.g., hardware)
                               // and we want to assume the pointer's validity after some sanity checks 
    let ret = entry(r0);
}

To run the harness with the CPROVER functions, I've used the below stub

# stub64.c
struct Unit ffi_CPROVER_DYNAMIC_OBJECT(unsigned long p) {
    __CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(p));
}

with the command cargo kani --enable-unstable --c-lib stub64.c.

@zpzigi754 zpzigi754 added the [C] Feature / Enhancement A new feature request or enhancement to an existing feature. label Mar 17, 2023
@zhassan-aws
Copy link
Contributor

@remi-delmas-3000 @tautschnig

My understanding is that __CPROVER_is_fresh was introduced for this purpose, but looks like it cannot be used outside of contracts?

@zpzigi754
Copy link
Author

In my trial, using __CPROVER_is_fresh outside of contracts didn't work.

@feliperodri
Copy link
Contributor

feliperodri commented Mar 30, 2023

__CPROVER_is_fresh was only designed to be used inside function contracts. @zhassan-aws @zpzigi754

You can see documentation for __CPROVER_is_fresh here.

I'm not sure what you are trying to achieve with ffi_CPROVER_DYNAMIC_OBJECT either. CBMC needs an allocation using malloc for every pointer. CBMC doesn't allow assumptions about pointer allocations. I think what you want is to design a function that allocates a0 using malloc.

cc. @tautschnig

@zhassan-aws
Copy link
Contributor

You can also perform the pointer allocation in the harness, e.g.

use std::alloc::{alloc, dealloc, Layout};

pub struct Context {
    pub len: u32,
}

fn entry(a0: usize) -> Result<usize, ()> {
    let ctx = a0 as *mut Context;
    unsafe {
        if (*ctx).len > 10 {
            // this is the violating condition
            return Err(());
        }
    }
    Ok(0)
}

#[kani::proof]
#[kani::unwind(64)]
fn main() {
    let layout = Layout::new::<Context>();
    let ptr = unsafe { alloc(layout) };
    unsafe {
        (*(ptr as *mut Context)).len = kani::any();
    }
    let r0 = ptr as usize;
    //let r0 = kani::any();      // let's say that this value is passed from an external interface (e.g., hardware)
    //                           // and we want to assume the pointer's validity after some sanity checks
    let ret = entry(r0);
    unsafe {
        dealloc(ptr, layout);
    }
}

Does this achieve what you're looking for?

@zpzigi754
Copy link
Author

@zhassan-aws, thank you a lot for the concrete example.
I guess that the suggestion looks similar to one of the alternative I have been using (assigning a local object in the harness).

pub struct Context {
    pub len: u32,
}

fn entry(a0: usize) -> Result<usize, ()> {
    let ctx = a0 as *mut Context;
    unsafe {
        if (*ctx).len > 10 {
            // this is the violating condition
            return Err(());
        }
    }
    Ok(0)
}

#[kani::proof]
#[kani::unwind(64)]
fn main() {
    let mut ctx = Context {
        len: kani::any(),
    };
    let r0 = &mut ctx as *mut Context as usize;
    let ret = entry(r0);
}

One problem in either using the local assignment or using alloc() is that it uses the object allocated from the receiver (B), not the object allocated from the sender (A). I think that assuming the validity of the passed object (pointer) is a bit different in semantic.

@zpzigi754
Copy link
Author

__CPROVER_is_fresh was only designed to be used inside function contracts. @zhassan-aws @zpzigi754

You can see documentation for __CPROVER_is_fresh here.

I'm not sure what you are trying to achieve with ffi_CPROVER_DYNAMIC_OBJECT either. CBMC needs an allocation using malloc for every pointer. CBMC doesn't allow assumptions about pointer allocations. I think what you want is to design a function that allocates a0 using malloc.

cc. @tautschnig

Thank you for the comment and the reference. What I want is giving an assumption about the pointer allocations, which might be against the CBMC's general discipline about dealing with pointers. Using malloc() could be a good option to use, but I hesitate using it because in my opinion it is semantically different.

@zhassan-aws
Copy link
Contributor

I guess that the suggestion looks similar to one of the alternative I have been using (assigning a local object in the harness).

Yes, the only difference is that in one, the pointer points to the stack (local variable), and in the other, the pointer points to the heap.

I think that assuming the validity of the passed object (pointer) is a bit different in semantic.

Can you clarify the semantics you're trying to achieve? Are you looking to create a pointer whose value itself is arbitrary? Is a model of the Sender included in the program?

@zpzigi754
Copy link
Author

zpzigi754 commented Apr 5, 2023

Can you clarify the semantics you're trying to achieve? Are you looking to create a pointer whose value itself is arbitrary? Is a model of the Sender included in the program?

Sure. Creating a pointer whose value itself is arbitrary is exactly what I want. The model of the Sender is not included in the program.

The below is the semantics I have in mind.

[semantics]

  • the target object should be valid (accessible) for the entire lifetime of the harness or a user-specified scope.
  • the target object should not be overwrapped with any other existing or future allocated objects
  • the target object should be readable and also could be writable with an additional option
  • the target object's location should be arbitrary

[optional]

  • the target object is actually in a different address space (different heap space)

@zhassan-aws
Copy link
Contributor

I see, thanks for the clarification. AFAIK, this cannot be achieved from Kani today as CBMC manages the addresses that are given to pointers. Some of the semantics you mention are achieved by __CPROVER_is_fresh though, so it might be possible through CBMC's contracts support.

CC @remi-delmas-3000 @tautschnig

@feliperodri feliperodri self-assigned this Feb 28, 2024
@celinval celinval changed the title How to assume the pointer's validity passed from an external interface? Add support for non-deterministic pointer Feb 29, 2024
@feliperodri feliperodri added this to the Function Contracts milestone Jun 7, 2024
@celinval
Copy link
Contributor

Hi @zpzigi754, we have recently added a PointerGenerator to generate pointers with Arbitrary allocation status. This is not quite yet completely arbitrary pointers, but it satisfies most of the semantics you posted.

Please let me know if that makes sense.

@zpzigi754
Copy link
Author

Hi @celinval, good to hear from you that there is a support for the pointers with the arbitrary allocation status. I appreciate your effort and the update.

celinval added a commit to model-checking/verify-rust-std that referenced this issue Dec 7, 2024
Here are a few limitations:
 1. Harness for`write_bytes` was disabled due to:
     - Issue model-checking/kani#90.
 2. The harnesses explicitly disable cases where a pointer is dangling.
- Kani cannot make assumptions on pointer allocation for dead or
dangling pointers (model-checking/kani#2300).
3. Actual intrinsics are very hard to verify with Kani. The cases we can
verify are those that have wrappers around the actual intrinsic.
     - Issue model-checking/kani#3345

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 and MIT licenses.

---------

Co-authored-by: Michael Tautschnig <mt@debian.org>
Co-authored-by: Michael Tautschnig <tautschn@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[C] Feature / Enhancement A new feature request or enhancement to an existing feature.
Projects
None yet
Development

No branches or pull requests

4 participants