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

Is it safe to implement SharedMemCast for bool? #33

Closed
asajeffrey opened this issue Sep 30, 2019 · 21 comments
Closed

Is it safe to implement SharedMemCast for bool? #33

asajeffrey opened this issue Sep 30, 2019 · 21 comments

Comments

@asajeffrey
Copy link

One of the requirements for bool is that it only has the values 0x00 and 0x01, so there is a possible safety issue around having bool implement SharedMemCast.

@asajeffrey
Copy link
Author

cc @nox

@elast0ny
Copy link
Owner

elast0ny commented Oct 2, 2019

Regular use of a bool that lives in the shared memory should be safe (ie: manipulated/initialized through the structs traits)

In contrast for example, the regular use of Vec in the shmem is unsafe as some Vec traits manipulate the backing allocation (alloc/realloc/etc...)

@elast0ny elast0ny closed this as completed Oct 2, 2019
@nox
Copy link

nox commented Oct 2, 2019

Regular use of a bool that lives in the shared memory should be safe (ie: manipulated/initialized through the structs traits)

How? Anything that crosses the process boundary should be untrusted, there is no guarantee that the value is actually a bool as Rust means it on the other side.

@nox
Copy link

nox commented Oct 2, 2019

This is UB and does not use any unsafe code. Please reopen this issue.

use ::shared_memory::*;
use std::ffi::{CStr, CString};

static GLOBAL_LOCK_ID: usize = 0;

fn main() -> Result<(), SharedMemError> {
    let mut shmem = match SharedMem::create_linked("shared_mem.link", LockType::Mutex, 4096) {
        // We created and own this mapping
        Ok(v) => v,
        // Link file already exists
        Err(SharedMemError::LinkExists) => SharedMem::open_linked("shared_mem.link")?,
        Err(e) => return Err(e),
    };

    {
        let mut shared_state = shmem.wlock::<u8>(GLOBAL_LOCK_ID)?;
        **shared_state = 2;
    }

    println!("{:?}", *shmem.rlock::<bool>(GLOBAL_LOCK_ID)?);
    Ok(())
}

@nox
Copy link

nox commented Oct 2, 2019

You can also do println!("{:?}", **shmem.rlock::<bool>(GLOBAL_LOCK_ID)? as u8); and see that it will print 2.

@nox
Copy link

nox commented Oct 2, 2019

Also note that for similar reasons, the implementations for char and str are also unsound.

@asajeffrey
Copy link
Author

+1 on reopening.

@nox
Copy link

nox commented Oct 2, 2019

I just realised that it also makes the impls for AtomicBool, AtomicPtr, AtomicUsize and AtomicIsize unsound, for the same reason that you can also read the bytes as a different type than you wrote them.

@asajeffrey
Copy link
Author

Not sure about that last comment, though it depends on what you think the contract for AtomicBool is. Why is AtomicUsize unsound?

@asajeffrey
Copy link
Author

Ah, it's because you can use a read-write lock to access the same shared memory both at type &usize and at type &AtomicUsize, then use the &AtomicUsize to write to the memory, which makes the &usize UB.

@asajeffrey
Copy link
Author

That raises a related issue, which is whether the implementation of Deref for ReadLockGuard is safe. I'll open a separate issue for that.

@elast0ny
Copy link
Owner

elast0ny commented Oct 2, 2019

I see what you're saying now. I will re-open the issue.

If I look at shared_memory's behavior with your point of view, then the whole crate is essentially unsafe. This is simply the inherent unsafety of sharing memory between two processes (regardless of languages)... Both processes have to agree exactly on what the memory represents or nothing will work, there is simply no way around this.

This can be clearly highlighted when using shared_memory in process1 in safe Rust and process2 in C. Process 2 can :

  • Mangle the metadata header values
  • Cast the shared memory to the wrong types
  • Ignore synchronization for data access
  • etc...

As highligted in this issue, some of these can be done with "safe" usage of this crate... This is also ignoring the fact that the rust ABI is not stable which could lead to even more subtle issues.

Im open to suggestions on how to fix this but unfortunately, i believe the only way to get the message across to the users of the crate is to mark essentially 90% of the API unsafe, eg :

  • Openning an existing mapping relies on the metadata being valid, this can crash a program if not
  • Any operations on locks/events are inherently unsafe as their state might be corrupted
  • Every read will be marked unsafe as the contents might be invalid for the chosen data type
  • Every write will be marked unsafe as there is no guarante another process isnt writing at the same place corrupting data

Unforunately, this would essentialy render the API a pain in the @$* to use...

Thoughts ?

@elast0ny elast0ny reopened this Oct 2, 2019
@asajeffrey
Copy link
Author

Thanks for re-opening!

One way of dealing this would be to be explicit about the attacker model: is the crate intended to be safe against all attackers, or just ones written in Rust? Defending against all attackers is the goal of shared-data, which I think I can see how to do apart from truncation :( asajeffrey/shared-data#7

I think a fair bit of the API for shared_memory is safe, it's the implementations of Deref and DerefMut that look dubious. At the expense of extra copying, I think it's safe (apart from issues with truncation) to use read_volatile to copy data out of shared memory, it's the access via &T that's an issue.

@nox
Copy link

nox commented Oct 2, 2019 via email

@asajeffrey
Copy link
Author

I guess even read_volatile will be UB if the shared memory is truncated in the middle of a read. Sigh, it would be nice if the OS would provide protection against file truncation.

@elast0ny
Copy link
Owner

elast0ny commented Oct 2, 2019

Yeap good point, I could strip the whole Deref/Casting feature and simply return raw pointers to the shared memory and let the callers handle those the way they want.

In addition, I would have to strip the lock/events out of the crate as they make a lot of assumptions about the shmem contents being valid but I was thinking of doing so anyways for #29

Essentialy, this would simplify the shared_memory crate to a thin wrapper for creating/openning shared memory mappings. Safely manipulating the shared memory would be 100% offloaded on the callers of this crate. Afterall, they are the only ones who can enforce proper initialization, sychronization and what specific offsets in the memory represents.

@asajeffrey
Copy link
Author

@elast0ny I can make a home for SharedMemCast in shared-data.

There's still the issue of truncation for anyone who ever wants to use one of the raw pointers, but that would then be my issue :)

@mitsuhiko
Copy link

I was playing around with this crate a bit to make it work with procspawn / mitosis. I think what would be possible to make safe is a pattern like this:

#[derive(SharedMemCast)]
pub struct SharedState {
    did_something: bool,
}

fn main() {
    procspawn::init();

    let region = shared_memory::Region::new(SharedState {
        did_something: false,
    });

    procspawn::spawn(region.clone(), |region| {
        let state = region.wlock().unwrap();
        state.did_something = true;
    }).join().unwrap();

    let state = region.rlock().unwrap();
    assert_eq!(state.did_something, true);
}

This way you can't unsafely have the wrong type on the subprocess. It's already easily possible to implement this type of thing on top of the systems that exist.

@nox
Copy link

nox commented Feb 10, 2020

What's shared_memory::Region?

@mitsuhiko
Copy link

@nox see #39 (renamed region to handle)

@elast0ny
Copy link
Owner

elast0ny commented May 8, 2020

This issue can finally be closed.

I have decided to remove all shared memory casting and exposed an Shmem::as_ptr() -> *mut u8 method on the mapping which callers can now cast to whatever they please.

This now removes all the safety issues highlighted in this thread from the crate.

@elast0ny elast0ny closed this as completed May 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants