comparison 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
comparison
equal deleted inserted replaced
135:b52594841480 136:efbc235f01d3
1 //! This module contains a few non-required helpers to deal with some of the
2 //! more annoying memory management in the PAM API.
3
4 use std::error::Error;
5 use std::marker::{PhantomData, PhantomPinned};
6 use std::mem::ManuallyDrop;
7 use std::ptr::NonNull;
8 use std::{any, fmt, mem, slice};
9
10 /// A pointer-to-pointer-to-message container for the [conversation callback].
11 ///
12 /// The PAM conversation callback requires a pointer to a pointer of [message]s.
13 /// Linux-PAM handles this differently than all other PAM implementations
14 /// (including the X/SSO PAM standard).
15 ///
16 /// X/SSO appears to specify a pointer-to-pointer-to-array:
17 ///
18 /// ```text
19 /// points to ┌────────────┐ ╔═ Message[] ═╗
20 /// messages ┄┄┄┄┄┄┄┄┄┄> │ *messages ┄┼┄┄┄┄┄> ║ style ║
21 /// └────────────┘ ║ data ┄┄┄┄┄┄┄╫┄┄> ...
22 /// ╟─────────────╢
23 /// ║ style ║
24 /// ║ data ┄┄┄┄┄┄┄╫┄┄> ...
25 /// ╟─────────────╢
26 /// ║ ... ║
27 /// ```
28 ///
29 /// whereas Linux-PAM uses an `**argv`-style pointer-to-array-of-pointers:
30 ///
31 /// ```text
32 /// points to ┌──────────────┐ ╔═ Message ═╗
33 /// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║
34 /// │ messages[1] ┄┼┄┄┄╮ ║ data ┄┄┄┄┄╫┄┄> ...
35 /// │ ... │ ┆ ╚═══════════╝
36 /// ┆
37 /// ┆ ╔═ Message ═╗
38 /// ╰┄┄> ║ style ║
39 /// ║ data ┄┄┄┄┄╫┄┄> ...
40 /// ╚═══════════╝
41 /// ```
42 ///
43 /// Because the `messages` remain owned by the application which calls into PAM,
44 /// we can solve this with One Simple Trick: make the intermediate list point
45 /// into the same array:
46 ///
47 /// ```text
48 /// points to ┌──────────────┐ ╔═ Message[] ═╗
49 /// messages ┄┄┄┄┄┄┄┄┄┄> │ messages[0] ┄┼┄┄┄┄> ║ style ║
50 /// │ messages[1] ┄┼┄┄╮ ║ data ┄┄┄┄┄┄┄╫┄┄> ...
51 /// │ ... │ ┆ ╟─────────────╢
52 /// ╰┄> ║ style ║
53 /// ║ data ┄┄┄┄┄┄┄╫┄┄> ...
54 /// ╟─────────────╢
55 /// ║ ... ║
56 ///
57 /// ```
58 ///
59 /// [conversation callback]: crate::ConversationCallback
60 /// [message]: crate::pam_message
61 #[derive(Debug)]
62 pub struct PtrPtrVec<T> {
63 data: Vec<T>,
64 pointers: Vec<*const T>,
65 }
66
67 // Since this is a wrapper around a Vec with no dangerous functionality*,
68 // this can be Send and Sync provided the original Vec is.
69 //
70 // * It will only become unsafe when the user dereferences a pointer or sends it
71 // to an unsafe function.
72 unsafe impl<T> Send for PtrPtrVec<T> where Vec<T>: Send {}
73 unsafe impl<T> Sync for PtrPtrVec<T> where Vec<T>: Sync {}
74
75 impl<T> PtrPtrVec<T> {
76 /// Takes ownership of the given Vec and creates a vec of pointers to it.
77 pub fn new(data: Vec<T>) -> Self {
78 let pointers: Vec<_> = data.iter().map(|r| r as *const T).collect();
79 Self { data, pointers }
80 }
81
82 /// Gives you back your Vec.
83 pub fn into_inner(self) -> Vec<T> {
84 self.data
85 }
86
87 /// Gets a pointer-to-pointer suitable for passing into the Conversation.
88 pub fn as_ptr<Dest>(&self) -> *const *const Dest {
89 Self::assert_size::<Dest>();
90 self.pointers.as_ptr().cast::<*const Dest>()
91 }
92
93 /// Iterates over a Linux-PAM–style pointer-to-array-of-pointers.
94 ///
95 /// # Safety
96 ///
97 /// `ptr_ptr` must be a valid pointer to an array of pointers,
98 /// there must be at least `count` valid pointers in the array,
99 /// and each pointer in that array must point to a valid `T`.
100 #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"]
101 #[allow(dead_code)]
102 pub unsafe fn iter_over_linux<'a, Src>(
103 ptr_ptr: *const *const Src,
104 count: usize,
105 ) -> impl Iterator<Item = &'a T>
106 where
107 T: 'a,
108 {
109 Self::assert_size::<Src>();
110 slice::from_raw_parts(ptr_ptr.cast::<&T>(), count)
111 .iter()
112 .copied()
113 }
114
115 /// Iterates over an X/SSO–style pointer-to-pointer-to-array.
116 ///
117 /// # Safety
118 ///
119 /// You must pass a valid pointer to a valid pointer to an array,
120 /// there must be at least `count` elements in the array,
121 /// and each value in that array must be a valid `T`.
122 #[deprecated = "use [`Self::iter_over`] instead, unless you really need this specific version"]
123 #[allow(dead_code)]
124 pub unsafe fn iter_over_xsso<'a, Src>(
125 ptr_ptr: *const *const Src,
126 count: usize,
127 ) -> impl Iterator<Item = &'a T>
128 where
129 T: 'a,
130 {
131 Self::assert_size::<Src>();
132 slice::from_raw_parts(*ptr_ptr.cast(), count).iter()
133 }
134
135 /// Iterates over a PAM message list appropriate to your system's impl.
136 ///
137 /// This selects the correct pointer/array structure to use for a message
138 /// that was given to you by your system.
139 ///
140 /// # Safety
141 ///
142 /// `ptr_ptr` must point to a valid message list, there must be at least
143 /// `count` messages in the list, and all messages must be a valid `Src`.
144 #[allow(deprecated)]
145 pub unsafe fn iter_over<'a, Src>(
146 ptr_ptr: *const *const Src,
147 count: usize,
148 ) -> impl Iterator<Item = &'a T>
149 where
150 T: 'a,
151 {
152 #[cfg(pam_impl = "LinuxPam")]
153 return Self::iter_over_linux(ptr_ptr, count);
154 #[cfg(not(pam_impl = "LinuxPam"))]
155 return Self::iter_over_xsso(ptr_ptr, count);
156 }
157
158 fn assert_size<That>() {
159 debug_assert_eq!(
160 mem::size_of::<T>(),
161 mem::size_of::<That>(),
162 "type {t} is not the size of {that}",
163 t = any::type_name::<T>(),
164 that = any::type_name::<That>(),
165 );
166 }
167 }
168
169 /// Error returned when attempting to allocate a buffer that is too big.
170 ///
171 /// This is specifically used in [`OwnedBinaryPayload`] when you try to allocate
172 /// a message larger than 2<sup>32</sup> bytes.
173 #[derive(Debug, PartialEq)]
174 pub struct TooBigError {
175 pub size: usize,
176 pub max: usize,
177 }
178
179 impl Error for TooBigError {}
180
181 impl fmt::Display for TooBigError {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 write!(
184 f,
185 "can't allocate a message of {size} bytes (max {max})",
186 size = self.size,
187 max = self.max
188 )
189 }
190 }
191
192 /// A trait wrapping memory management.
193 ///
194 /// This is intended to allow you to bring your own allocator for
195 /// [`OwnedBinaryPayload`]s.
196 ///
197 /// For an implementation example, see the implementation of this trait
198 /// for [`Vec`].
199 pub trait Buffer<T: Default> {
200 /// Allocates a buffer of `len` elements, filled with the default.
201 fn allocate(len: usize) -> Self;
202
203 fn as_ptr(&self) -> *const T;
204
205 /// Returns a slice view of `size` elements of the given memory.
206 ///
207 /// # Safety
208 ///
209 /// The caller must not request more elements than are allocated.
210 unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T];
211
212 /// Consumes this ownership and returns a pointer to the start of the arena.
213 fn into_ptr(self) -> NonNull<T>;
214
215 /// "Adopts" the memory at the given pointer, taking it under management.
216 ///
217 /// Running the operation:
218 ///
219 /// ```
220 /// # use libpam_sys::helpers::Buffer;
221 /// # fn test<T: Default, OwnerType: Buffer<T>>(bytes: usize) {
222 /// let owner = OwnerType::allocate(bytes);
223 /// let ptr = owner.into_ptr();
224 /// let owner = unsafe { OwnerType::from_ptr(ptr, bytes) };
225 /// # }
226 /// ```
227 ///
228 /// must be a no-op.
229 ///
230 /// # Safety
231 ///
232 /// The pointer must be valid, and the caller must provide the exact size
233 /// of the given arena.
234 unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self;
235 }
236
237 impl<T: Default> Buffer<T> for Vec<T> {
238 fn allocate(bytes: usize) -> Self {
239 (0..bytes).map(|_| Default::default()).collect()
240 }
241
242 fn as_ptr(&self) -> *const T {
243 Vec::as_ptr(self)
244 }
245
246 unsafe fn as_mut_slice(&mut self, bytes: usize) -> &mut [T] {
247 debug_assert!(bytes <= self.len());
248 Vec::as_mut(self)
249 }
250
251 fn into_ptr(self) -> NonNull<T> {
252 let mut me = ManuallyDrop::new(self);
253 // SAFETY: a Vec is guaranteed to have a nonzero pointer.
254 unsafe { NonNull::new_unchecked(me.as_mut_ptr()) }
255 }
256
257 unsafe fn from_ptr(ptr: NonNull<T>, bytes: usize) -> Self {
258 Vec::from_raw_parts(ptr.as_ptr(), bytes, bytes)
259 }
260 }
261
262 /// The structure of the "binary message" payload for the `PAM_BINARY_PROMPT`
263 /// extension from Linux-PAM.
264 pub struct BinaryPayload {
265 /// The total byte size of the message, including this header,
266 /// as a u32 in network byte order (big endian).
267 pub total_bytes_u32be: [u8; 4],
268 /// A tag used to provide some kind of hint as to what the data is.
269 /// Its meaning is undefined.
270 pub data_type: u8,
271 /// Where the data itself would start, used as a marker to make this
272 /// not [`Unpin`] (since it is effectively an intrusive data structure
273 /// pointing to immediately after itself).
274 pub _marker: PhantomData<PhantomPinned>,
275 }
276
277 impl BinaryPayload {
278 /// The most data it's possible to put into a [`BinaryPayload`].
279 pub const MAX_SIZE: usize = (u32::MAX - 5) as usize;
280
281 /// Fills in the provided buffer with the given data.
282 ///
283 /// This uses [`copy_from_slice`](slice::copy_from_slice) internally,
284 /// so `buf` must be exactly 5 bytes longer than `data`, or this function
285 /// will panic.
286 pub fn fill(buf: &mut [u8], data_type: u8, data: &[u8]) {
287 let ptr: *mut Self = buf.as_mut_ptr().cast();
288 // SAFETY: We're given a slice, which always has a nonzero pointer.
289 let me = unsafe { ptr.as_mut().unwrap_unchecked() };
290 me.total_bytes_u32be = u32::to_be_bytes(buf.len() as u32);
291 me.data_type = data_type;
292 buf[5..].copy_from_slice(data)
293 }
294
295 /// The total storage needed for the message, including header.
296 pub fn total_bytes(&self) -> usize {
297 u32::from_be_bytes(self.total_bytes_u32be) as usize
298 }
299
300 /// Gets the total byte buffer of the BinaryMessage stored at the pointer.
301 ///
302 /// The returned data slice is borrowed from where the pointer points to.
303 ///
304 /// # Safety
305 ///
306 /// - The pointer must point to a valid `BinaryPayload`.
307 /// - The borrowed data must not outlive the pointer's validity.
308 pub unsafe fn buffer_of<'a>(ptr: *const Self) -> &'a [u8] {
309 let header: &Self = ptr.as_ref().unwrap_unchecked();
310 slice::from_raw_parts(ptr.cast(), header.total_bytes().max(5))
311 }
312
313 /// Gets the contents of the BinaryMessage stored at the given pointer.
314 ///
315 /// The returned data slice is borrowed from where the pointer points to.
316 /// This is a cheap operation and doesn't do *any* copying.
317 ///
318 /// We don't take a `&self` reference here because accessing beyond
319 /// the range of the `Self` data (i.e., beyond the 5 bytes of `self`)
320 /// is undefined behavior. Instead, you have to pass a raw pointer
321 /// directly to the data.
322 ///
323 /// # Safety
324 ///
325 /// - The pointer must point to a valid `BinaryPayload`.
326 /// - The borrowed data must not outlive the pointer's validity.
327 pub unsafe fn contents<'a>(ptr: *const Self) -> (u8, &'a [u8]) {
328 let header: &Self = ptr.as_ref().unwrap_unchecked();
329 (header.data_type, &Self::buffer_of(ptr)[5..])
330 }
331 }
332
333 /// A binary message owned by some storage.
334 ///
335 /// This is an owned, memory-managed version of [`BinaryPayload`].
336 /// The `O` type manages the memory where the payload lives.
337 /// [`Vec<u8>`] is one such manager and can be used when ownership
338 /// of the data does not need to transit through PAM.
339 #[derive(Debug)]
340 pub struct OwnedBinaryPayload<Owner: Buffer<u8>>(Owner);
341
342 impl<O: Buffer<u8>> OwnedBinaryPayload<O> {
343 /// Allocates a new OwnedBinaryPayload.
344 ///
345 /// This will return a [`TooBigError`] if you try to allocate too much
346 /// (more than [`BinaryPayload::MAX_SIZE`]).
347 pub fn new(data_type: u8, data: &[u8]) -> Result<Self, TooBigError> {
348 let total_len: u32 = (data.len() + 5).try_into().map_err(|_| TooBigError {
349 size: data.len(),
350 max: BinaryPayload::MAX_SIZE,
351 })?;
352 let total_len = total_len as usize;
353 let mut buf = O::allocate(total_len);
354 // SAFETY: We just allocated this exact size.
355 BinaryPayload::fill(unsafe { buf.as_mut_slice(total_len) }, data_type, data);
356 Ok(Self(buf))
357 }
358
359 /// The contents of the buffer.
360 pub fn contents(&self) -> (u8, &[u8]) {
361 unsafe { BinaryPayload::contents(self.as_ptr()) }
362 }
363
364 /// The total bytes needed to store this, including the header.
365 pub fn total_bytes(&self) -> usize {
366 unsafe { BinaryPayload::buffer_of(self.0.as_ptr().cast()).len() }
367 }
368
369 /// Unwraps this into the raw storage backing it.
370 pub fn into_inner(self) -> O {
371 self.0
372 }
373
374 /// Gets a const pointer to the start of the message's buffer.
375 pub fn as_ptr(&self) -> *const BinaryPayload {
376 self.0.as_ptr().cast()
377 }
378
379 /// Consumes ownership of this message and converts it to a raw pointer
380 /// to the start of the message.
381 ///
382 /// To clean this up, you should eventually pass it into [`Self::from_ptr`]
383 /// with the same `O` ownership type.
384 pub fn into_ptr(self) -> NonNull<BinaryPayload> {
385 self.0.into_ptr().cast()
386 }
387
388 /// Takes ownership of the given pointer.
389 ///
390 /// # Safety
391 ///
392 /// You must provide a valid pointer, allocated by (or equivalent to one
393 /// allocated by) [`Self::new`]. For instance, passing a pointer allocated
394 /// by `malloc` to `OwnedBinaryPayload::<Vec<u8>>::from_ptr` is not allowed.
395 pub unsafe fn from_ptr(ptr: NonNull<BinaryPayload>) -> Self {
396 Self(O::from_ptr(ptr.cast(), ptr.as_ref().total_bytes()))
397 }
398 }
399
400 #[cfg(test)]
401 mod tests {
402 use super::*;
403 use std::ptr;
404
405 type VecPayload = OwnedBinaryPayload<Vec<u8>>;
406
407 #[test]
408 fn test_binary_payload() {
409 let simple_message = &[0u8, 0, 0, 16, 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
410 let empty = &[0u8; 5];
411
412 assert_eq!((0xff, &[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..]), unsafe {
413 BinaryPayload::contents(simple_message.as_ptr().cast())
414 });
415 assert_eq!((0x00, &[][..]), unsafe {
416 BinaryPayload::contents(empty.as_ptr().cast())
417 });
418 }
419
420 #[test]
421 fn test_owned_binary_payload() {
422 let (typ, data) = (
423 112,
424 &[0, 1, 1, 8, 9, 9, 9, 8, 8, 1, 9, 9, 9, 1, 1, 9, 7, 2, 5, 3][..],
425 );
426 let payload = VecPayload::new(typ, data).unwrap();
427 assert_eq!((typ, data), payload.contents());
428 let ptr = payload.into_ptr();
429 let payload = unsafe { VecPayload::from_ptr(ptr) };
430 assert_eq!((typ, data), payload.contents());
431 }
432
433 #[test]
434 #[ignore]
435 fn test_owned_too_big() {
436 let data = vec![0xFFu8; 0x1_0000_0001];
437 assert_eq!(
438 TooBigError {
439 max: 0xffff_fffa,
440 size: 0x1_0000_0001
441 },
442 VecPayload::new(5, &data).unwrap_err()
443 )
444 }
445
446 #[cfg(debug_assertions)]
447 #[test]
448 #[should_panic]
449 fn test_new_wrong_size() {
450 let bad_vec = vec![0; 19];
451 let msg = PtrPtrVec::new(bad_vec);
452 let _ = msg.as_ptr::<u64>();
453 }
454
455 #[allow(deprecated)]
456 #[cfg(debug_assertions)]
457 #[test]
458 #[should_panic]
459 fn test_iter_xsso_wrong_size() {
460 unsafe {
461 let _ = PtrPtrVec::<u8>::iter_over_xsso::<f64>(ptr::null(), 1);
462 }
463 }
464
465 #[allow(deprecated)]
466 #[cfg(debug_assertions)]
467 #[test]
468 #[should_panic]
469 fn test_iter_linux_wrong_size() {
470 unsafe {
471 let _ = PtrPtrVec::<u128>::iter_over_linux::<()>(ptr::null(), 1);
472 }
473 }
474
475 #[allow(deprecated)]
476 #[test]
477 fn test_right_size() {
478 let good_vec = vec![(1u64, 2u64), (3, 4), (5, 6)];
479 let ptr = good_vec.as_ptr();
480 let msg = PtrPtrVec::new(good_vec);
481 let msg_ref: *const *const (i64, i64) = msg.as_ptr();
482 assert_eq!(unsafe { *msg_ref }, ptr.cast());
483
484 let linux_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_linux(msg_ref, 3) }
485 .cloned()
486 .collect();
487 let xsso_result: Vec<(i64, i64)> = unsafe { PtrPtrVec::iter_over_xsso(msg_ref, 3) }
488 .cloned()
489 .collect();
490 assert_eq!(vec![(1, 2), (3, 4), (5, 6)], linux_result);
491 assert_eq!(vec![(1, 2), (3, 4), (5, 6)], xsso_result);
492 drop(msg)
493 }
494
495 #[allow(deprecated)]
496 #[test]
497 fn test_iter_ptr_ptr() {
498 let strs = vec![Box::new("a"), Box::new("b"), Box::new("c"), Box::new("D")];
499 let ptr: *const *const &str = strs.as_ptr().cast();
500 let got: Vec<&str> = unsafe { PtrPtrVec::iter_over_linux(ptr, 4) }
501 .cloned()
502 .collect();
503 assert_eq!(vec!["a", "b", "c", "D"], got);
504
505 let nums = [-1i8, 2, 3];
506 let ptr = nums.as_ptr();
507 let got: Vec<u8> = unsafe { PtrPtrVec::iter_over_xsso(&ptr, 3) }
508 .cloned()
509 .collect();
510 assert_eq!(vec![255, 2, 3], got);
511 }
512 }