Mercurial > crates > nonstick
diff libpam-sys/src/helpers.rs @ 136:efbc235f01d3 default tip
Separate libpam-sys-helpers from libpam-sys.
This separates the parts of libpam-sys that don't need linking against libpam
from the parts that do need to link against libpam.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 03 Jul 2025 14:28:04 -0400 |
parents | b52594841480 |
children |
line wrap: on
line diff
--- a/libpam-sys/src/helpers.rs Thu Jul 03 11:14:49 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,512 +0,0 @@ -//! This module contains a few non-required helpers to deal with some of the -//! more annoying memory management in the PAM API. - -use std::error::Error; -use std::marker::{PhantomData, PhantomPinned}; -use std::mem::ManuallyDrop; -use std::ptr::NonNull; -use std::{any, fmt, mem, slice}; - -/// A pointer-to-pointer-to-message container for the [conversation callback]. -/// -/// The PAM conversation callback requires a pointer to a pointer of [message]s. -/// Linux-PAM handles this differently than all other PAM implementations -/// (including the X/SSO PAM standard). -/// -/// X/SSO appears to specify a pointer-to-pointer-to-array: -/// -/// ```text -/// points to ┌────────────┐ ╔═ Message[] ═╗ -/// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║ -/// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// ╟─────────────╢ -/// ║ style ║ -/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// ╟─────────────╢ -/// ║ ... ║ -/// ``` -/// -/// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers: -/// -/// ```text -/// points to ┌──────────────┐ ╔═ Message ═╗ -/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ -/// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ... -/// │ ... │ ┆ ╚═══════════╝ -/// ┆ -/// ┆ ╔═ Message ═╗ -/// ╰┄┄> ║ style ║ -/// ║ data ┄┄┄┄┄╫┄┄> ... -/// ╚═══════════╝ -/// ``` -/// -/// Because the `messages` remain owned by the application which calls into PAM, -/// we can solve this with One Simple Trick: make the intermediate list point -/// into the same array: -/// -/// ```text -/// points to ┌──────────────┐ ╔═ Message[] ═╗ -/// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║ -/// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// │ ... │ ┆ ╟─────────────╢ -/// ╰┄> ║ style ║ -/// ║ data ┄┄┄┄┄┄┄╫┄┄> ... -/// ╟─────────────╢ -/// ║ ... ║ -/// -/// ``` -/// -/// [conversation callback]: crate::ConversationCallback -/// [message]: crate::pam_message -#[derive(Debug)] -pub struct PtrPtrVec<T> { - data: Vec<T>, - pointers: Vec<*const T>, -} - -// Since this is a wrapper around a Vec with no dangerous functionality*, -// this can be Send and Sync provided the original Vec is. -// -// * It will only become unsafe when the user dereferences a pointer or sends it -// to an unsafe function. -unsafe impl<T> Send for PtrPtrVec<T> where Vec<T>: Send {} -unsafe impl<T> Sync for PtrPtrVec<T> where Vec<T>: Sync {} - -impl<T> PtrPtrVec<T> { - /// Takes ownership of the given Vec and creates a vec of pointers to it. - pub fn new(data: Vec<T>) -> Self { - let pointers: Vec<_> = data.iter().map(|r| r as *const T).collect(); - Self { data, pointers } - } - - /// Gives you back your Vec. - pub fn into_inner(self) -> Vec<T> { - self.data - } - - /// Gets a pointer-to-pointer suitable for passing into the Conversation. - pub fn as_ptr<Dest>(&self) -> *const *const Dest { - Self::assert_size::<Dest>(); - self.pointers.as_ptr().cast::<*const Dest>() - } - - /// Iterates over a Linux-PAM–style pointer-to-array-of-pointers. - /// - /// # Safety - /// - /// `ptr_ptr` must be a valid pointer to an array of pointers, - /// there must be at least `count` valid pointers in the array, - /// and each pointer in that array must point to a valid `T`. - #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] - #[allow(dead_code)] - pub unsafe fn iter_over_linux<'a, Src>( - ptr_ptr: *const *const Src, - count: usize, - ) -> impl Iterator<Item = &'a T> - where - T: 'a, - { - Self::assert_size::<Src>(); - slice::from_raw_parts(ptr_ptr.cast::<&T>(), count) - .iter() - .copied() - } - - /// Iterates over an X/SSO–style pointer-to-pointer-to-array. - /// - /// # Safety - /// - /// You must pass a valid pointer to a valid pointer to an array, - /// there must be at least `count` elements in the array, - /// and each value in that array must be a valid `T`. - #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"] - #[allow(dead_code)] - pub unsafe fn iter_over_xsso<'a, Src>( - ptr_ptr: *const *const Src, - count: usize, - ) -> impl Iterator<Item = &'a T> - where - T: 'a, - { - Self::assert_size::<Src>(); - slice::from_raw_parts(*ptr_ptr.cast(), count).iter() - } - - /// Iterates over a PAM message list appropriate to your system's impl. - /// - /// This selects the correct pointer/array structure to use for a message - /// that was given to you by your system. - /// - /// # Safety - /// - /// `ptr_ptr` must point to a valid message list, there must be at least - /// `count` messages in the list, and all messages must be a valid `Src`. - #[allow(deprecated)] - pub unsafe fn iter_over<'a, Src>( - ptr_ptr: *const *const Src, - count: usize, - ) -> impl Iterator<Item = &'a T> - where - T: 'a, - { - #[cfg(pam_impl = "LinuxPam")] - return Self::iter_over_linux(ptr_ptr, count); - #[cfg(not(pam_impl = "LinuxPam"))] - return Self::iter_over_xsso(ptr_ptr, count); - } - - fn assert_size<That>() { - debug_assert_eq!( - mem::size_of::<T>(), - mem::size_of::<That>(), - "type {t} is not the size of {that}", - t = any::type_name::<T>(), - that = any::type_name::<That>(), - ); - } -} - -/// Error returned when attempting to allocate a buffer that is too big. -/// -/// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate -/// a message larger than 2<sup>32</sup> bytes. -#[derive(Debug, PartialEq)] -pub struct TooBigError { - pub size: usize, - pub max: usize, -} - -impl Error for TooBigError {} - -impl fmt::Display for TooBigError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "can't allocate a message of {size} bytes (max {max})", - size = self.size, - max = self.max - ) - } -} - -/// A trait wrapping memory management. -/// -/// This is intended to allow you to bring your own allocator for -/// [`OwnedBinaryPayload`]s. -/// -/// For an implementation example, see the implementation of this trait -/// for [`Vec`]. -pub trait Buffer<T: Default> { - /// Allocates a buffer of `len` elements, filled with the default. - fn allocate(len: usize) -> Self; - - fn as_ptr(&self) -> *const T; - - /// Returns a slice view of `size` elements of the given memory. - /// - /// # Safety - /// - /// The caller must not request more elements than are allocated. - unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T]; - - /// Consumes this ownership and returns a pointer to the start of the arena. - fn into_ptr(self) -> NonNull<T>; - - /// "Adopts" the memory at the given pointer, taking it under management. - /// - /// Running the operation: - /// - /// ``` - /// # use libpam_sys::helpers::Buffer; - /// # fn test<T: Default, OwnerType: Buffer<T>>(bytes: usize) { - /// let owner = OwnerType::allocate(bytes); - /// let ptr = owner.into_ptr(); - /// let owner = unsafe { OwnerType::from_ptr(ptr, bytes) }; - /// # } - /// ``` - /// - /// must be a no-op. - /// - /// # Safety - /// - /// The pointer must be valid, and the caller must provide the exact size - /// of the given arena. - unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self; -} - -impl<T: Default> Buffer<T> for Vec<T> { - fn allocate(bytes: usize) -> Self { - (0..bytes).map(|_| Default::default()).collect() - } - - fn as_ptr(&self) -> *const T { - Vec::as_ptr(self) - } - - unsafe fn as_mut_slice(&mut self, bytes: usize) -> &mut [T] { - debug_assert!(bytes <= self.len()); - Vec::as_mut(self) - } - - fn into_ptr(self) -> NonNull<T> { - let mut me = ManuallyDrop::new(self); - // SAFETY: a Vec is guaranteed to have a nonzero pointer. - unsafe { NonNull::new_unchecked(me.as_mut_ptr()) } - } - - unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self { - Vec::from_raw_parts(ptr.as_ptr(), bytes, bytes) - } -} - -/// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT` -/// extension from Linux-PAM. -pub struct BinaryPayload { - /// The total byte size of the message, including this header, - /// as a u32 in network byte order (big endian). - pub total_bytes_u32be: [u8; 4], - /// A tag used to provide some kind of hint as to what the data is. - /// Its meaning is undefined. - pub data_type: u8, - /// Where the data itself would start, used as a marker to make this - /// not [`Unpin`] (since it is effectively an intrusive data structure - /// pointing to immediately after itself). - pub _marker: PhantomData<PhantomPinned>, -} - -impl BinaryPayload { - /// The most data it's possible to put into a [`BinaryPayload`]. - pub const MAX_SIZE: usize = (u32::MAX - 5) as usize; - - /// Fills in the provided buffer with the given data. - /// - /// This uses [`copy_from_slice`](slice::copy_from_slice) internally, - /// so `buf` must be exactly 5 bytes longer than `data`, or this function - /// will panic. - pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) { - let ptr: *mut Self = buf.as_mut_ptr().cast(); - // SAFETY: We're given a slice, which always has a nonzero pointer. - let me = unsafe { ptr.as_mut().unwrap_unchecked() }; - me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32); - me.data_type = data_type; - buf[5..].copy_from_slice(data) - } - - /// The total storage needed for the message, including header. - pub fn total_bytes(&self) -> usize { - u32::from_be_bytes(self.total_bytes_u32be) as usize - } - - /// Gets the total byte buffer of the BinaryMessage stored at the pointer. - /// - /// The returned data slice is borrowed from where the pointer points to. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The borrowed data must not outlive the pointer's validity. - pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5)) - } - - /// Gets the contents of the BinaryMessage stored at the given pointer. - /// - /// The returned data slice is borrowed from where the pointer points to. - /// This is a cheap operation and doesn't do *any* copying. - /// - /// We don't take a `&self` reference here because accessing beyond - /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`) - /// is undefined behavior. Instead, you have to pass a raw pointer - /// directly to the data. - /// - /// # Safety - /// - /// - The pointer must point to a valid `BinaryPayload`. - /// - The borrowed data must not outlive the pointer's validity. - pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) { - let header: &Self = ptr.as_ref().unwrap_unchecked(); - (header.data_type, &Self::buffer_of(ptr)[5..]) - } -} - -/// A binary message owned by some storage. -/// -/// This is an owned, memory-managed version of [`BinaryPayload`]. -/// The `O` type manages the memory where the payload lives. -/// [`Vec<u8>`] is one such manager and can be used when ownership -/// of the data does not need to transit through PAM. -#[derive(Debug)] -pub struct OwnedBinaryPayload<Owner: Buffer<u8>>(Owner); - -impl<O: Buffer<u8>> OwnedBinaryPayload<O> { - /// Allocates a new OwnedBinaryPayload. - /// - /// This will return a [`TooBigError`] if you try to allocate too much - /// (more than [`BinaryPayload::MAX_SIZE`]). - pub fn new(data_type: u8, data: &[u8]) -> Result<Self, TooBigError> { - let total_len: u32 = (data.len() + 5).try_into().map_err(|_| TooBigError { - size: data.len(), - max: BinaryPayload::MAX_SIZE, - })?; - let total_len = total_len as usize; - let mut buf = O::allocate(total_len); - // SAFETY: We just allocated this exact size. - BinaryPayload::fill(unsafe { buf.as_mut_slice(total_len) }, data_type, data); - Ok(Self(buf)) - } - - /// The contents of the buffer. - pub fn contents(&self) -> (u8, &[u8]) { - unsafe { BinaryPayload::contents(self.as_ptr()) } - } - - /// The total bytes needed to store this, including the header. - pub fn total_bytes(&self) -> usize { - unsafe { BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() } - } - - /// Unwraps this into the raw storage backing it. - pub fn into_inner(self) -> O { - self.0 - } - - /// Gets a const pointer to the start of the message's buffer. - pub fn as_ptr(&self) -> *const BinaryPayload { - self.0.as_ptr().cast() - } - - /// Consumes ownership of this message and converts it to a raw pointer - /// to the start of the message. - /// - /// To clean this up, you should eventually pass it into [`Self::from_ptr`] - /// with the same `O` ownership type. - pub fn into_ptr(self) -> NonNull<BinaryPayload> { - self.0.into_ptr().cast() - } - - /// Takes ownership of the given pointer. - /// - /// # Safety - /// - /// You must provide a valid pointer, allocated by (or equivalent to one - /// allocated by) [`Self::new`]. For instance, passing a pointer allocated - /// by `malloc` to `OwnedBinaryPayload::<Vec<u8>>::from_ptr` is not allowed. - pub unsafe fn from_ptr(ptr: NonNull<BinaryPayload>) -> Self { - Self(O::from_ptr(ptr.cast(), ptr.as_ref().total_bytes())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::ptr; - - type VecPayload = OwnedBinaryPayload<Vec<u8>>; - - #[test] - fn test_binary_payload() { - let simple_message = &[0u8, 0, 0, 16, 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let empty = &[0u8; 5]; - - assert_eq!((0xff, &[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..]), unsafe { - BinaryPayload::contents(simple_message.as_ptr().cast()) - }); - assert_eq!((0x00, &[][..]), unsafe { - BinaryPayload::contents(empty.as_ptr().cast()) - }); - } - - #[test] - fn test_owned_binary_payload() { - let (typ, data) = ( - 112, - &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..], - ); - let payload = VecPayload::new(typ, data).unwrap(); - assert_eq!((typ, data), payload.contents()); - let ptr = payload.into_ptr(); - let payload = unsafe { VecPayload::from_ptr(ptr) }; - assert_eq!((typ, data), payload.contents()); - } - - #[test] - #[ignore] - fn test_owned_too_big() { - let data = vec![0xFFu8; 0x1_0000_0001]; - assert_eq!( - TooBigError { - max: 0xffff_fffa, - size: 0x1_0000_0001 - }, - VecPayload::new(5, &data).unwrap_err() - ) - } - - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn test_new_wrong_size() { - let bad_vec = vec![0; 19]; - let msg = PtrPtrVec::new(bad_vec); - let _ = msg.as_ptr::<u64>(); - } - - #[allow(deprecated)] - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn test_iter_xsso_wrong_size() { - unsafe { - let _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1); - } - } - - #[allow(deprecated)] - #[cfg(debug_assertions)] - #[test] - #[should_panic] - fn test_iter_linux_wrong_size() { - unsafe { - let _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1); - } - } - - #[allow(deprecated)] - #[test] - fn test_right_size() { - let good_vec = vec![(1u64, 2u64), (3, 4), (5, 6)]; - let ptr = good_vec.as_ptr(); - let msg = PtrPtrVec::new(good_vec); - let msg_ref: *const *const (i64, i64) = msg.as_ptr(); - assert_eq!(unsafe { *msg_ref }, ptr.cast()); - - let linux_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_linux(msg_ref, 3) } - .cloned() - .collect(); - let xsso_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_xsso(msg_ref, 3) } - .cloned() - .collect(); - assert_eq!(vec![(1, 2), (3, 4), (5, 6)], linux_result); - assert_eq!(vec![(1, 2), (3, 4), (5, 6)], xsso_result); - drop(msg) - } - - #[allow(deprecated)] - #[test] - fn test_iter_ptr_ptr() { - let strs = vec![Box::new("a"), Box::new("b"), Box::new("c"), Box::new("D")]; - let ptr: *const *const &str = strs.as_ptr().cast(); - let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) } - .cloned() - .collect(); - assert_eq!(vec!["a", "b", "c", "D"], got); - - let nums = [-1i8, 2, 3]; - let ptr = nums.as_ptr(); - let got: Vec<u8> = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) } - .cloned() - .collect(); - assert_eq!(vec![255, 2, 3], got); - } -}