Skip to main content

shadow_pod/
lib.rs

1//! Utilities for working with POD (Plain Old Data)
2
3#![cfg_attr(not(any(test, feature = "std")), no_std)]
4// https://github.com/rust-lang/rfcs/blob/master/text/2585-unsafe-block-in-unsafe-fn.md
5#![deny(unsafe_op_in_unsafe_fn)]
6
7use core::mem::MaybeUninit;
8
9/// Marker trait that the given type is Plain Old Data; i.e. that it is safe to
10/// interpret any pattern of bits as a value of this type.
11///
12/// This is notably *not* true for many Rust types. e.g. interpreting the integer
13/// value `2` as a rust `bool` is undefined behavior.
14///
15/// We require `Copy` to also rule out anything that implements `Drop`.
16///
17/// References are inherently non-Pod, so we can require a 'static lifetime.
18///
19/// This is very *similar* in concept to `bytemuck::AnyBitPattern`. However,
20/// unlike `AnyBitPattern`, this trait does not say anything about how the type
21/// can be safely shared. e.g. while `bytemuck::AnyBitPattern` disallows pointer
22/// types, [`Pod`] does not.
23///
24/// # Safety
25///
26/// - Any pattern of bits must be a valid value of the given type.
27/// - The type must not contain an [`UnsafeCell`](core::cell::UnsafeCell), or any other structure
28///   that contains an `UnsafeCell` (for example [`Cell`](core::cell::Cell)). Otherwise the following
29///   code would have UB:
30///   ```ignore
31///   let x = Cell::new(0);
32///   let y = as_u8_slice(&x);
33///   x.set(1);
34///   ```
35pub unsafe trait Pod: Copy + 'static {}
36
37/// Convert to a slice of raw bytes.
38///
39/// Some bytes may be uninitialized if T has padding.
40pub fn to_u8_slice<T>(slice: &[T]) -> &[MaybeUninit<u8>]
41where
42    T: Pod,
43{
44    // SAFETY: Any value and alignment is safe for u8.
45    unsafe {
46        core::slice::from_raw_parts(
47            slice.as_ptr() as *const MaybeUninit<u8>,
48            slice.len() * core::mem::size_of::<MaybeUninit<T>>(),
49        )
50    }
51}
52
53/// Cast as a slice of raw bytes.
54///
55/// Some bytes may be uninitialized if T has padding.
56pub fn as_u8_slice<T>(x: &T) -> &[MaybeUninit<u8>]
57where
58    T: Pod,
59{
60    to_u8_slice(core::slice::from_ref(x))
61}
62
63/// Convert to a mut slice of raw bytes.
64///
65/// Some bytes may be uninialized if T has padding.
66///
67/// # Safety
68///
69/// Uninitialized bytes (e.g. [`MaybeUninit::uninit`]) must not be written
70/// into the returned slice, which would invalidate the source `slice`.
71pub unsafe fn to_u8_slice_mut<T>(slice: &mut [T]) -> &mut [MaybeUninit<u8>]
72where
73    T: Pod,
74{
75    // SAFETY: Any value and alignment is safe for u8.
76    unsafe {
77        core::slice::from_raw_parts_mut(
78            slice.as_mut_ptr() as *mut MaybeUninit<u8>,
79            slice.len() * core::mem::size_of::<MaybeUninit<T>>(),
80        )
81    }
82}
83
84/// Cast as a mut slice of raw bytes.
85///
86/// Some bytes may be uninitialized if T has padding.
87///
88/// # Safety
89///
90/// See [`to_u8_slice_mut`].
91pub unsafe fn as_u8_slice_mut<T>(x: &mut T) -> &mut [MaybeUninit<u8>]
92where
93    T: Pod,
94{
95    unsafe { to_u8_slice_mut(core::slice::from_mut(x)) }
96}
97
98/// Create a value of type `T`, with contents initialized to 0s.
99pub fn zeroed<T>() -> T
100where
101    T: Pod,
102{
103    // SAFETY: Any value is legal for Pod.
104    unsafe { core::mem::zeroed() }
105}
106
107/// Wrapper type to support associated compile-time size checks
108struct PodTransmute<const N: usize, T> {
109    _t: core::marker::PhantomData<T>,
110}
111
112impl<const N: usize, T: Pod> PodTransmute<N, T> {
113    const CHECK: () = assert!(N == core::mem::size_of::<T>());
114    #[inline(always)]
115    fn transmute_array(x: &[u8; N]) -> T {
116        // this should perform a compile-time check
117        #[allow(clippy::let_unit_value)]
118        let _ = Self::CHECK;
119
120        // this should perform a runtime check in case the above compile-time check didn't run, but
121        // should be compiled out if the compile-time check did run
122        assert_eq!(N, core::mem::size_of::<T>());
123
124        // It'd be nice to use `transmute` here, and take the array by value,
125        // but there's no way to convince the type system that the input and output
126        // sizes are guaranteed to be equal. So, we use `transmute_copy` which
127        // doesn't require this to be statically guaranteed.
128        unsafe { core::mem::transmute_copy(x) }
129    }
130}
131
132/// Interpret the bytes of `x` as a value of type `T`.
133pub fn from_array<const N: usize, T: Pod>(x: &[u8; N]) -> T {
134    PodTransmute::transmute_array(x)
135}
136
137#[cfg(feature = "std")]
138pub trait ReadExt {
139    /// Receive a `T`, or `None` if EOF is reached without reading any bytes.
140    ///
141    /// If EOF is reached in the middle of a value, an error with
142    /// ErrorKind::UnexpectedEof is returned.
143    fn read_pod<T: Pod>(&mut self) -> std::io::Result<Option<T>>;
144}
145
146#[cfg(feature = "std")]
147impl<R> ReadExt for R
148where
149    R: std::io::Read,
150{
151    fn read_pod<T: Pod>(&mut self) -> std::io::Result<Option<T>> {
152        let mut dst = std::mem::MaybeUninit::<T>::uninit();
153        // SAFETY: we don't write any uninitialized bytes into the returned slice.
154        let dst_slice = unsafe { as_u8_slice_mut(&mut dst) };
155        dst_slice.fill(MaybeUninit::new(0u8));
156        // SAFETY: all bytes are now initialized.
157        let dst_slice = unsafe { dst_slice.assume_init_mut() };
158
159        // Try to read all the bytes.
160        // We *don't* use Read::read_all here, since it doesn't allow us to
161        // distinguish between the "read nothing" case and the "read partial"
162        // case.
163        let mut total_read = 0;
164        while total_read != dst_slice.len() {
165            let nread = self.read(&mut dst_slice[total_read..])?;
166            if nread == 0 {
167                return if total_read != 0 {
168                    Err(std::io::Error::new(
169                        std::io::ErrorKind::UnexpectedEof,
170                        "EOF in middle of value",
171                    ))
172                } else {
173                    Ok(None)
174                };
175            }
176            total_read += nread;
177        }
178        // SAFETY: we've initialized all bytes, and any bit pattern is a valid T
179        // because T is Pod.
180        Ok(Some(unsafe { dst.assume_init() }))
181    }
182}
183
184#[cfg(feature = "std")]
185pub trait WriteExt {
186    /// Write `val`.
187    fn write_pod<T: Pod>(&mut self, val: &T) -> std::io::Result<()>;
188}
189
190#[cfg(feature = "std")]
191impl<W> WriteExt for W
192where
193    // It'd be nice to implement for std::io::Write, but it's unclear how to do
194    // so while dealing with padding bytes in the source value (which are
195    // uninitialized).
196    W: std::os::fd::AsRawFd,
197{
198    fn write_pod<T: Pod>(&mut self, val: &T) -> std::io::Result<()> {
199        let bytes = as_u8_slice(val);
200        let mut total_written = 0;
201        while total_written != bytes.len() {
202            // SAFETY: we're passing this pointer directly to a Linux syscall,
203            // which shouldn't care that some of the memory it's reading is
204            // "uninitialized" from a rust compiler standpoint.
205            //
206            // We don't use `libc::write` or even `libc::syscall`, since it's
207            // conceivable that in some environment, the libc implementation
208            // could be statically linked and access the memory in user-space,
209            // which could result in undefined behavior.
210            //
211            // Unfortunately that makes this function impossible to analyze in
212            // miri, which doesn't know how to interpret the raw syscall. (Its
213            // stub for libc::write doesn't permit unitialized bytes, presumably
214            // for the reason above).
215            use linux_syscall::Result64 as _;
216            let nwritten = unsafe {
217                linux_syscall::syscall!(
218                    linux_syscall::SYS_write,
219                    self.as_raw_fd(),
220                    bytes.as_ptr().add(total_written),
221                    bytes.len() - total_written,
222                )
223            }
224            .try_u64()
225            .map_err(|x| std::io::Error::from_raw_os_error(x.get().into()))?;
226            total_written += usize::try_from(nwritten).unwrap();
227        }
228        Ok(())
229    }
230}
231
232// Integer primitives
233unsafe impl Pod for u8 {}
234unsafe impl Pod for u16 {}
235unsafe impl Pod for u32 {}
236unsafe impl Pod for u64 {}
237unsafe impl Pod for i8 {}
238unsafe impl Pod for i16 {}
239unsafe impl Pod for i32 {}
240unsafe impl Pod for i64 {}
241unsafe impl Pod for isize {}
242unsafe impl Pod for usize {}
243
244// No! Values other than 0 or 1 are invalid.
245// impl !Pod for bool {}
246
247// No! `char` must be a valid unicode value.
248// impl !Pod for char {}
249
250unsafe impl<T> Pod for core::mem::MaybeUninit<T> where T: Pod {}
251unsafe impl<T, const N: usize> Pod for [T; N] where T: Pod {}
252
253// libc types
254unsafe impl Pod for libc::Dl_info {}
255unsafe impl Pod for libc::Elf32_Chdr {}
256unsafe impl Pod for libc::Elf32_Ehdr {}
257unsafe impl Pod for libc::Elf32_Phdr {}
258unsafe impl Pod for libc::Elf32_Shdr {}
259unsafe impl Pod for libc::Elf32_Sym {}
260unsafe impl Pod for libc::Elf64_Chdr {}
261unsafe impl Pod for libc::Elf64_Ehdr {}
262unsafe impl Pod for libc::Elf64_Phdr {}
263unsafe impl Pod for libc::Elf64_Shdr {}
264unsafe impl Pod for libc::Elf64_Sym {}
265unsafe impl Pod for libc::__c_anonymous_sockaddr_can_j1939 {}
266unsafe impl Pod for libc::__c_anonymous_sockaddr_can_tp {}
267unsafe impl Pod for libc::__exit_status {}
268unsafe impl Pod for libc::__timeval {}
269unsafe impl Pod for libc::_libc_fpstate {}
270unsafe impl Pod for libc::_libc_fpxreg {}
271unsafe impl Pod for libc::_libc_xmmreg {}
272unsafe impl Pod for libc::addrinfo {}
273//unsafe impl Pod for libc::af_alg_i {}
274unsafe impl Pod for libc::aiocb {}
275unsafe impl Pod for libc::arpd_request {}
276unsafe impl Pod for libc::arphdr {}
277unsafe impl Pod for libc::arpreq {}
278unsafe impl Pod for libc::arpreq_old {}
279unsafe impl Pod for libc::can_filter {}
280unsafe impl Pod for libc::can_frame {}
281unsafe impl Pod for libc::canfd_frame {}
282unsafe impl Pod for libc::cmsghdr {}
283unsafe impl Pod for libc::cpu_set_t {}
284unsafe impl Pod for libc::dirent {}
285unsafe impl Pod for libc::dirent64 {}
286unsafe impl Pod for libc::dl_phdr_info {}
287unsafe impl Pod for libc::dqblk {}
288unsafe impl Pod for libc::epoll_event {}
289unsafe impl Pod for libc::fanotify_event_metadata {}
290unsafe impl Pod for libc::fanotify_response {}
291unsafe impl Pod for libc::fd_set {}
292unsafe impl Pod for libc::ff_condition_effect {}
293unsafe impl Pod for libc::ff_constant_effect {}
294unsafe impl Pod for libc::ff_effect {}
295unsafe impl Pod for libc::ff_envelope {}
296unsafe impl Pod for libc::ff_periodic_effect {}
297unsafe impl Pod for libc::ff_ramp_effect {}
298unsafe impl Pod for libc::ff_replay {}
299unsafe impl Pod for libc::ff_rumble_effect {}
300unsafe impl Pod for libc::ff_trigger {}
301unsafe impl Pod for libc::flock {}
302unsafe impl Pod for libc::flock64 {}
303unsafe impl Pod for libc::fsid_t {}
304unsafe impl Pod for libc::genlmsghdr {}
305unsafe impl Pod for libc::glob64_t {}
306unsafe impl Pod for libc::glob_t {}
307unsafe impl Pod for libc::group {}
308unsafe impl Pod for libc::hostent {}
309unsafe impl Pod for libc::if_nameindex {}
310unsafe impl Pod for libc::ifaddrs {}
311unsafe impl Pod for libc::in6_addr {}
312unsafe impl Pod for libc::in6_pktinfo {}
313unsafe impl Pod for libc::in6_rtmsg {}
314unsafe impl Pod for libc::in_addr {}
315unsafe impl Pod for libc::in_pktinfo {}
316unsafe impl Pod for libc::inotify_event {}
317unsafe impl Pod for libc::input_absinfo {}
318unsafe impl Pod for libc::input_event {}
319unsafe impl Pod for libc::input_id {}
320unsafe impl Pod for libc::input_keymap_entry {}
321unsafe impl Pod for libc::input_mask {}
322unsafe impl Pod for libc::iovec {}
323unsafe impl Pod for libc::ip_mreq {}
324unsafe impl Pod for libc::ip_mreq_source {}
325unsafe impl Pod for libc::ip_mreqn {}
326unsafe impl Pod for libc::ipc_perm {}
327unsafe impl Pod for libc::ipv6_mreq {}
328unsafe impl Pod for libc::itimerspec {}
329unsafe impl Pod for libc::itimerval {}
330unsafe impl Pod for libc::lconv {}
331unsafe impl Pod for libc::linger {}
332unsafe impl Pod for libc::mallinfo {}
333unsafe impl Pod for libc::max_align_t {}
334unsafe impl Pod for libc::mcontext_t {}
335unsafe impl Pod for libc::mmsghdr {}
336unsafe impl Pod for libc::mntent {}
337unsafe impl Pod for libc::mq_attr {}
338unsafe impl Pod for libc::msghdr {}
339unsafe impl Pod for libc::msginfo {}
340unsafe impl Pod for libc::msqid_ds {}
341unsafe impl Pod for libc::nl_mmap_hdr {}
342unsafe impl Pod for libc::nl_mmap_req {}
343unsafe impl Pod for libc::nl_pktinfo {}
344unsafe impl Pod for libc::nlattr {}
345unsafe impl Pod for libc::nlmsgerr {}
346unsafe impl Pod for libc::nlmsghdr {}
347unsafe impl Pod for libc::ntptimeval {}
348unsafe impl Pod for libc::packet_mreq {}
349unsafe impl Pod for libc::passwd {}
350unsafe impl Pod for libc::pollfd {}
351unsafe impl Pod for libc::posix_spawn_file_actions_t {}
352unsafe impl Pod for libc::posix_spawnattr_t {}
353unsafe impl Pod for libc::protoent {}
354unsafe impl Pod for libc::pthread_attr_t {}
355unsafe impl Pod for libc::pthread_cond_t {}
356unsafe impl Pod for libc::pthread_condattr_t {}
357unsafe impl Pod for libc::pthread_mutex_t {}
358unsafe impl Pod for libc::pthread_mutexattr_t {}
359unsafe impl Pod for libc::pthread_rwlock_t {}
360unsafe impl Pod for libc::pthread_rwlockattr_t {}
361unsafe impl Pod for libc::regex_t {}
362unsafe impl Pod for libc::regmatch_t {}
363unsafe impl Pod for libc::rlimit {}
364unsafe impl Pod for libc::rlimit64 {}
365unsafe impl Pod for libc::rtentry {}
366unsafe impl Pod for libc::rusage {}
367unsafe impl Pod for libc::sched_param {}
368unsafe impl Pod for libc::sem_t {}
369unsafe impl Pod for libc::sembuf {}
370unsafe impl Pod for libc::servent {}
371unsafe impl Pod for libc::shmid_ds {}
372unsafe impl Pod for libc::sigaction {}
373unsafe impl Pod for libc::sigevent {}
374unsafe impl Pod for libc::siginfo_t {}
375unsafe impl Pod for libc::signalfd_siginfo {}
376unsafe impl Pod for libc::sigset_t {}
377unsafe impl Pod for libc::sigval {}
378unsafe impl Pod for libc::sock_extended_err {}
379unsafe impl Pod for libc::sockaddr {}
380unsafe impl Pod for libc::sockaddr_alg {}
381unsafe impl Pod for libc::sockaddr_can {}
382unsafe impl Pod for libc::sockaddr_in {}
383unsafe impl Pod for libc::sockaddr_in6 {}
384unsafe impl Pod for libc::sockaddr_ll {}
385unsafe impl Pod for libc::sockaddr_nl {}
386unsafe impl Pod for libc::sockaddr_storage {}
387unsafe impl Pod for libc::sockaddr_un {}
388unsafe impl Pod for libc::sockaddr_vm {}
389unsafe impl Pod for libc::spwd {}
390unsafe impl Pod for libc::stack_t {}
391unsafe impl Pod for libc::stat {}
392unsafe impl Pod for libc::stat64 {}
393unsafe impl Pod for libc::statfs {}
394unsafe impl Pod for libc::statfs64 {}
395unsafe impl Pod for libc::statvfs {}
396unsafe impl Pod for libc::statvfs64 {}
397unsafe impl Pod for libc::statx {}
398unsafe impl Pod for libc::statx_timestamp {}
399unsafe impl Pod for libc::sysinfo {}
400unsafe impl Pod for libc::termios {}
401unsafe impl Pod for libc::termios2 {}
402unsafe impl Pod for libc::timespec {}
403unsafe impl Pod for libc::timeval {}
404unsafe impl Pod for libc::timex {}
405unsafe impl Pod for libc::tm {}
406unsafe impl Pod for libc::tms {}
407unsafe impl Pod for libc::ucontext_t {}
408unsafe impl Pod for libc::ucred {}
409unsafe impl Pod for libc::uinput_abs_setup {}
410unsafe impl Pod for libc::uinput_ff_erase {}
411unsafe impl Pod for libc::uinput_ff_upload {}
412unsafe impl Pod for libc::uinput_setup {}
413unsafe impl Pod for libc::uinput_user_dev {}
414unsafe impl Pod for libc::user {}
415unsafe impl Pod for libc::user_fpregs_struct {}
416unsafe impl Pod for libc::user_regs_struct {}
417unsafe impl Pod for libc::utimbuf {}
418unsafe impl Pod for libc::utmpx {}
419unsafe impl Pod for libc::utsname {}
420unsafe impl Pod for libc::winsize {}
421unsafe impl Pod for libc::clone_args {}
422
423#[cfg(test)]
424mod test {
425    use super::*;
426    use std::io::Write;
427
428    /// A type guaranteed to have some padding bytes.
429    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
430    #[repr(C)]
431    struct TypeWithPadding {
432        begin: u8,
433        middle: u64,
434        end: u8,
435    }
436    unsafe impl Pod for TypeWithPadding {}
437
438    /// A reader that reads the buffer its given, resulting in miri-detectable
439    /// unsoundness if it's uninitialized.
440    struct NosyReader<R>(R);
441    impl<R> std::io::Read for NosyReader<R>
442    where
443        R: std::io::Read,
444    {
445        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
446            // The "nosy" access to the input buffer bytes, which miri should
447            // detect as unsound if they're uninitialized.
448            println!("overwriting {buf:?}");
449            self.0.read(buf)
450        }
451    }
452
453    /// A reader that reads at-most 1 byte at a time, to exercise read-loops.
454    struct SlowReader<R>(R);
455    impl<R> std::io::Read for SlowReader<R>
456    where
457        R: std::io::Read,
458    {
459        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
460            // Only access the first byte.
461            let buf = match buf.get_mut(0..1) {
462                Some(x) => x,
463                None => buf,
464            };
465            self.0.read(buf)
466        }
467    }
468
469    #[cfg(feature = "std")]
470    #[test]
471    fn test_read_pod() {
472        let (reader, mut writer) = std::io::pipe().unwrap();
473        let bytes = [0u8; std::mem::size_of::<TypeWithPadding>()];
474        writer.write_all(&bytes).unwrap();
475
476        let read_value =
477            super::ReadExt::read_pod::<TypeWithPadding>(&mut NosyReader(SlowReader(reader)))
478                .unwrap();
479        assert_eq!(
480            read_value,
481            Some(TypeWithPadding {
482                begin: 0,
483                middle: 0,
484                end: 0
485            })
486        );
487    }
488
489    // TODO: convince miri that `write_pod` is sound.
490    #[cfg(all(feature = "std", not(miri)))]
491    #[test]
492    fn test_write_pod() {
493        let (reader, mut writer) = std::io::pipe().unwrap();
494        let val = TypeWithPadding {
495            begin: 1u8,
496            middle: 0x1122334455667788u64,
497            end: 2u8,
498        };
499        writer.write_pod(&val).unwrap();
500        let read_value =
501            super::ReadExt::read_pod::<TypeWithPadding>(&mut NosyReader(SlowReader(reader)))
502                .unwrap();
503        assert_eq!(read_value, Some(val));
504    }
505}