Mercurial > crates > nonstick
changeset 119:476a22db8639
Add PtrPtrVec to make it easy to pass pointer-to-pointers to PAM.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Mon, 30 Jun 2025 01:40:28 -0400 |
parents | 39760dfc9b3b |
children | 0f913ec120ac |
files | libpam-sys/src/helpers.rs libpam-sys/src/structs.rs |
diffstat | 2 files changed, 241 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/libpam-sys/src/helpers.rs Sun Jun 29 20:13:03 2025 -0400 +++ b/libpam-sys/src/helpers.rs Mon Jun 30 01:40:28 2025 -0400 @@ -5,7 +5,178 @@ use std::marker::{PhantomData, PhantomPinned}; use std::mem::ManuallyDrop; use std::ptr::NonNull; -use std::{fmt, slice}; +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::Message +#[derive(Debug)] +pub struct PtrPtrVec<T> { + data: Vec<T>, + pointers: Vec<*const T>, +} + +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() + } + + #[crate::cfg_pam_impl("LinuxPam")] + unsafe fn _iter_over<'a, Src>( + ptr_ptr: *const *const Src, + count: usize, + ) -> impl Iterator<Item = &'a T> + where + T: 'a, + { + #[allow(deprecated)] + Self::iter_over_linux(ptr_ptr, count) + } + + #[crate::cfg_pam_impl(not("LinuxPam"))] + unsafe fn _iter_over<'a, Src>( + ptr_ptr: *const *const Src, + count: usize, + ) -> impl Iterator<Item = &'a T> + where + T: 'a, + { + #[allow(deprecated)] + Self::iter_over_xsso(ptr_ptr, count) + } + + /// 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`. + pub unsafe fn iter_over<'a, Src>( + ptr_ptr: *const *const Src, + count: usize, + ) -> impl Iterator<Item = &'a T> + where + T: 'a, + { + Self::_iter_over(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. /// @@ -241,6 +412,7 @@ #[cfg(test)] mod tests { use super::*; + use std::ptr; type VecPayload = OwnedBinaryPayload<Vec<u8>>; @@ -282,4 +454,67 @@ VecPayload::new(5, &data).unwrap_err() ) } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn test_new_wrong_size() { + let bad_vec = vec![0; 19]; + let msg = PtrPtrVec::new(bad_vec); + let _ = msg.as_ptr::<u64>(); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn test_iter_xsso_wrong_size() { + unsafe { + _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1); + } + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn test_iter_linux_wrong_size() { + unsafe { + _ = 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 = vec![-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); + } }
--- a/libpam-sys/src/structs.rs Sun Jun 29 20:13:03 2025 -0400 +++ b/libpam-sys/src/structs.rs Mon Jun 30 01:40:28 2025 -0400 @@ -44,7 +44,12 @@ /// see the [`helpers`](crate::helpers) module. pub type ConversationCallback = unsafe extern "C" fn( num_msg: c_int, + // This is a *const *const because accessing memory from a reference + // outside its bounds is undefined behavior, and *messages is an array + // in X/SSO PAM impls. messages: *const *const Message, + // This is a &mut *mut because the caller sets the pointer in `responses` + // but does not mess around outside its memory space. responses: &mut *mut Response, appdata: *const AppData, ) -> c_int;