I have an application where I have a type declared like:
/// Shared reference for a fixed-length slice of secret bytes that will be zeroed
/// when all references are dropped.
pub struct FixedLengthSecretBytes<const N: usize>(Arc<Zeroizing<[u8; N]>>);
In my application I need to expose the bytes wrapped by this type over a foreign-function interface exposed by a cdylib. I do so using Arc::into_raw and Arc::from_raw. The C layer doesn't have a way to interpret Zeroizing, so I need to return a raw pointer to primitive bytes.
What I want to write for this is roughly something like:
impl<const N: usize> FixedLengthSecretBytes<N> {
pub fn into_raw(self) -> *const [u8; N] {
let inner: * const Zeroizing<[u8; N]> = Arc::into_raw(self.0);
// cast pointer to discard zeroizing wrapper. This implicitly assumes that the layout of Zeroizing<T> is the
// same as T.
inner.cast()
}
pub unsafe fn from_raw(ptr: *const [u8; N]) -> Self {
let ptr = ptr as *const Zeroizing<[u8; N]>; // same layout assumption required here.
let arc = unsafe { Arc::from_raw(ptr) };
Self(arc)
}
}
In practice the code above works today because Zeroizing<[u8; N]> has the same physical layout as [u8; N], but I believe this code is technically UB (or is at least relying on unspecified compiler behavior) because Zeroizing is repr(Rust), which means its layout is not guaranteed to be the same as its underlying type. I think I can make into_raw work as-is with a bit more cleverness by dereferencing down to the inner slice and then extracting a pointer from there, but from_raw is harder to do in a way that's free of these assumptions.
I believe that adding a repr(transparent) annotation on Zeroizing would make the casts here well-defined. Is that something that could be considered here? I believe such a change would be backwards-compatible. The main downside is that it would make it easier for unsafe code like this to extract values out of a Zeroizing, which could circumvent the guarantees provided by that type, but that's already possible if you're dropping down to unsafe, and providing a transparent guarantee would make it possible for code like this to be as safe as possible given the inherent unsafety of FFI.
I have an application where I have a type declared like:
In my application I need to expose the bytes wrapped by this type over a foreign-function interface exposed by a
cdylib. I do so usingArc::into_rawandArc::from_raw. The C layer doesn't have a way to interpretZeroizing, so I need to return a raw pointer to primitive bytes.What I want to write for this is roughly something like:
In practice the code above works today because
Zeroizing<[u8; N]>has the same physical layout as[u8; N], but I believe this code is technically UB (or is at least relying on unspecified compiler behavior) becauseZeroizingisrepr(Rust), which means its layout is not guaranteed to be the same as its underlying type. I think I can makeinto_rawwork as-is with a bit more cleverness by dereferencing down to the inner slice and then extracting a pointer from there, butfrom_rawis harder to do in a way that's free of these assumptions.I believe that adding a
repr(transparent)annotation onZeroizingwould make the casts here well-defined. Is that something that could be considered here? I believe such a change would be backwards-compatible. The main downside is that it would make it easier for unsafe code like this to extract values out of aZeroizing, which could circumvent the guarantees provided by that type, but that's already possible if you're dropping down tounsafe, and providing a transparent guarantee would make it possible for code like this to be as safe as possible given the inherent unsafety of FFI.