Pinning a Boxed slice of an Unpin type

Hi all, it's my first time posting here :slight_smile:

I'm having an issue regarding keeping a heap-allocated slice in a struct with a PhantomPinned field. My goal is to work with some sensitive bytes (e.g. representing a private key), and ensure that they are not copied anywhere. Existing crates for this kind of thing secrecy, zeroizing, don't pin the data, so if it's stack-allocated it gets copied around, and if it's heap-allocated there's still no semantic guarantees that it won't move.

My first approach was simply using a Pin<Box<[u8]>>, but since [u8] is Unpin, that doesn't help.

So I created the following, with the goal of using Unmovable<[u8]> to keep my data in:

use std::marker::PhantomPinned;
use std::pin::Pin;

struct WithPhantomPin<T: ?Sized> {
    _phantom: PhantomPinned,
    inner: T,
}

pub struct Unmovable<T: ?Sized>(Pin<Box<WithPhantomPin<T>>>);

impl<T: ?Sized> Unmovable<T> {
    pub fn inner(&self) -> &T {
        &(*self.0).inner
    }

    pub fn inner_mut(&mut self) -> &mut T {
        let mut_with_phantom_pin: &mut WithPhantomPin<T> =
            unsafe { self.0.as_mut().get_unchecked_mut() };
        &mut mut_with_phantom_pin.inner
    }
}

However, I'm now stuck on how to create a new Unmovable<[u8]> with the right length, so that I can fill the private data into it. I can use Box::new_uninit_slice (or new_zeroed_slice), to get a heap-allocated slice of the right size, but then I can't put that slice into my WithPhantomPin struct.

The Pin type really does not gain you anything here at all. This is not its purpose. Just use a Box<[u8]>.

Thanks for your reply.

As I understood it, all types in Rust are by default freely-movable, which is very much not what I want here, and using Pin is a way to stop that from happening.

If I use a Box<[u8]>, the semantics say the [u8] can be moved in memory, no?

No, Pin is a normal type. It doesn't tell the compiler anything new. Box has the guarantee that it won't move things around in memory.

For example, Sequoia PGP uses a wrapper around *mut [u8] to prevent this problem: mem.rs.html -- source

That optimization doesn't actually happen. Besides, if it did, using Pin would not prevent it because Pin is not a special type.

A raw pointer probably wouldn't prevent it either. The optimizer in LLVM understands raw pointers and if it can see the full range in which the raw pointer is used, it could optimize based on that.

For example, if you are writing asynchronous code and pin a future somewhere, and then proceed to do stuff with it that doesn't involve moving the future, then the compiler is absolutely allowed to introduce optimizations that move the future around. The only requirement is that the behavior is indistinguishable from the version where it is not moved.

What Pin actually does is use module privacy to prevent users that use only safe code from explicitly inserting a move or swap operation. It does not do anything to prevent the optimizer from inserting moves.

I think if you really want to have a wrapper type to prevent the data from getting copied to somewhere else, to start with you probably want to avoid those public inner() and inner_mut() methods, and only have methods that operate against the data in place.

Well, I need to be able to fill the buffer with stuff, so I'm going to need to expose a &mut [u8] somewhere. Like others have said though, Pin isn't getting me anything here, so I'll just use Box instead.

It would be good to be able to guarantee that the data is not going to be moved around on the heap through compiler optimisations at any point though, but I guess that's just not possible when using Rust.

Generally, the same problems are why you can't write crypto code that runs in constant-time in any language but assembly.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.