comparison src/libpam/memory.rs @ 139:33b9622ed6d2

Remove redundant memory management in nonstick::libpam; fix UB. - Uses the libpam-sys-helpers BinaryPayload / OwnedBinaryPayload structs to handle memory management and parsing for Linux-PAM binary messages. - Gets rid of the (technically) undefined behavior in PtrPtrVec due to pointer provenance. - Don't check for malloc failing. It won't, even if it does. - Formatting/cleanups/etc.
author Paul Fisher <paul@pfish.zone>
date Thu, 03 Jul 2025 23:57:49 -0400
parents 49d9e2b5c189
children a508a69c068a
comparison
equal deleted inserted replaced
138:999bf07efbcb 139:33b9622ed6d2
1 //! Things for dealing with memory. 1 //! Things for dealing with memory.
2 2
3 use crate::ErrorCode;
3 use crate::Result; 4 use crate::Result;
4 use crate::{BinaryData, ErrorCode}; 5 use libpam_sys_helpers::memory::{Buffer, OwnedBinaryPayload};
5 use memoffset::offset_of;
6 use std::error::Error;
7 use std::ffi::{c_char, CStr, CString}; 6 use std::ffi::{c_char, CStr, CString};
8 use std::fmt::{Display, Formatter, Result as FmtResult};
9 use std::marker::{PhantomData, PhantomPinned}; 7 use std::marker::{PhantomData, PhantomPinned};
10 use std::mem::ManuallyDrop; 8 use std::mem::ManuallyDrop;
11 use std::ops::{Deref, DerefMut}; 9 use std::ops::{Deref, DerefMut};
12 use std::ptr::NonNull; 10 use std::ptr::NonNull;
13 use std::result::Result as StdResult;
14 use std::{mem, ptr, slice}; 11 use std::{mem, ptr, slice};
15
16 /// Raised from `calloc` when you have no memory!
17 #[derive(Debug)]
18 pub struct NoMem;
19
20 impl Display for NoMem {
21 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
22 write!(f, "out of memory!")
23 }
24 }
25
26 impl Error for NoMem {}
27
28 impl From<NoMem> for ErrorCode {
29 fn from(_: NoMem) -> Self {
30 ErrorCode::BufferError
31 }
32 }
33 12
34 /// Allocates `count` elements to hold `T`. 13 /// Allocates `count` elements to hold `T`.
35 #[inline] 14 #[inline]
36 pub fn calloc<T>(count: usize) -> StdResult<NonNull<T>, NoMem> { 15 pub fn calloc<T>(count: usize) -> NonNull<T> {
37 // SAFETY: it's always safe to allocate! Leaking memory is fun! 16 // SAFETY: it's always safe to allocate! Leaking memory is fun!
38 NonNull::new(unsafe { libc::calloc(count, mem::size_of::<T>()) }.cast()).ok_or(NoMem) 17 unsafe { NonNull::new_unchecked(libc::calloc(count, mem::size_of::<T>()).cast()) }
39 } 18 }
40 19
41 /// Wrapper for [`libc::free`] to make debugging calls/frees easier. 20 /// Wrapper for [`libc::free`] to make debugging calls/frees easier.
42 /// 21 ///
43 /// # Safety 22 /// # Safety
77 // Lots of "as" and "into" associated functions. 56 // Lots of "as" and "into" associated functions.
78 #[allow(clippy::wrong_self_convention)] 57 #[allow(clippy::wrong_self_convention)]
79 impl<T> CHeapBox<T> { 58 impl<T> CHeapBox<T> {
80 /// Creates a new CHeapBox holding the given data. 59 /// Creates a new CHeapBox holding the given data.
81 pub fn new(value: T) -> Result<Self> { 60 pub fn new(value: T) -> Result<Self> {
82 let memory = calloc(1)?; 61 let memory = calloc(1);
83 unsafe { ptr::write(memory.as_ptr(), value) } 62 unsafe { ptr::write(memory.as_ptr(), value) }
84 // SAFETY: We literally just allocated this. 63 // SAFETY: We literally just allocated this.
85 Ok(Self(memory)) 64 Ok(Self(memory))
86 } 65 }
87 66
105 /// You are responsible for ensuring the CHeapBox lives long enough. 84 /// You are responsible for ensuring the CHeapBox lives long enough.
106 pub fn as_ptr(this: &Self) -> NonNull<T> { 85 pub fn as_ptr(this: &Self) -> NonNull<T> {
107 this.0 86 this.0
108 } 87 }
109 88
89 /// Because it's annoying to type `CHeapBox.as_ptr(...).as_ptr()`.
90 pub fn as_raw_ptr(this: &Self) -> *mut T {
91 this.0.as_ptr()
92 }
93
110 /// Converts this into a Box of a different type. 94 /// Converts this into a Box of a different type.
111 /// 95 ///
112 /// # Safety 96 /// # Safety
113 /// 97 ///
114 /// The different type has to be compatible in size/alignment and drop behavior. 98 /// The different type has to be compatible in size/alignment and drop behavior.
120 impl<T: Default> Default for CHeapBox<T> { 104 impl<T: Default> Default for CHeapBox<T> {
121 fn default() -> Self { 105 fn default() -> Self {
122 Self::new(Default::default()).expect("allocation should not fail") 106 Self::new(Default::default()).expect("allocation should not fail")
123 } 107 }
124 } 108 }
109
110 impl Buffer for CHeapBox<u8> {
111 fn allocate(len: usize) -> Self {
112 // SAFETY: This is all freshly-allocated memory!
113 unsafe { Self::from_ptr(calloc(len)) }
114 }
115
116 fn as_ptr(this: &Self) -> *const u8 {
117 this.0.as_ptr()
118 }
119
120 unsafe fn as_mut_slice(this: &mut Self, len: usize) -> &mut [u8] {
121 slice::from_raw_parts_mut(this.0.as_ptr(), len)
122 }
123
124 fn into_ptr(this: Self) -> NonNull<u8> {
125 CHeapBox::into_ptr(this)
126 }
127
128 unsafe fn from_ptr(ptr: NonNull<u8>, _: usize) -> Self {
129 CHeapBox::from_ptr(ptr)
130 }
131 }
132
133 pub type CHeapPayload = OwnedBinaryPayload<CHeapBox<u8>>;
125 134
126 impl<T> Deref for CHeapBox<T> { 135 impl<T> Deref for CHeapBox<T> {
127 type Target = T; 136 type Target = T;
128 fn deref(&self) -> &Self::Target { 137 fn deref(&self) -> &Self::Target {
129 // SAFETY: We own this pointer and it is guaranteed valid. 138 // SAFETY: We own this pointer and it is guaranteed valid.
162 let data = text.as_bytes(); 171 let data = text.as_bytes();
163 if data.contains(&0) { 172 if data.contains(&0) {
164 return Err(ErrorCode::ConversationError); 173 return Err(ErrorCode::ConversationError);
165 } 174 }
166 // +1 for the null terminator 175 // +1 for the null terminator
167 let data_alloc: NonNull<c_char> = calloc(data.len() + 1)?; 176 let data_alloc: NonNull<c_char> = calloc(data.len() + 1);
168 // SAFETY: we just allocated this and we have enough room. 177 // SAFETY: we just allocated this and we have enough room.
169 unsafe { 178 unsafe {
170 libc::memcpy(data_alloc.as_ptr().cast(), data.as_ptr().cast(), data.len()); 179 let dest = slice::from_raw_parts_mut(data_alloc.as_ptr().cast(), data.len());
180 dest.copy_from_slice(data);
171 Ok(Self(CHeapBox::from_ptr(data_alloc))) 181 Ok(Self(CHeapBox::from_ptr(data_alloc)))
172 } 182 }
173 } 183 }
174 184
175 /// Converts this C heap string into a raw pointer. 185 /// Converts this C heap string into a raw pointer.
246 None => return Ok(None), 256 None => return Ok(None),
247 }; 257 };
248 Ok(borrowed.map(String::from)) 258 Ok(borrowed.map(String::from))
249 } 259 }
250 260
251 /// Binary data used in requests and responses.
252 ///
253 /// This is an unsized data type whose memory goes beyond its data.
254 /// This must be allocated on the C heap.
255 ///
256 /// A Linux-PAM extension.
257 #[repr(C)]
258 pub struct CBinaryData {
259 /// The total length of the structure; a u32 in network byte order (BE).
260 total_length: [u8; 4],
261 /// A tag of undefined meaning.
262 data_type: u8,
263 /// Pointer to an array of length [`length`](Self::length) − 5
264 data: [u8; 0],
265 _marker: Immovable,
266 }
267
268 impl CBinaryData {
269 /// Copies the given data to a new BinaryData on the heap.
270 pub fn alloc((data, data_type): (&[u8], u8)) -> Result<CHeapBox<CBinaryData>> {
271 let buffer_size =
272 u32::try_from(data.len() + 5).map_err(|_| ErrorCode::ConversationError)?;
273 // SAFETY: We're only allocating here.
274 unsafe {
275 let mut dest_buffer: NonNull<Self> = calloc::<u8>(buffer_size as usize)?.cast();
276 let dest = dest_buffer.as_mut();
277 dest.total_length = buffer_size.to_be_bytes();
278 dest.data_type = data_type;
279 libc::memcpy(
280 Self::data_ptr(dest_buffer).cast(),
281 data.as_ptr().cast(),
282 data.len(),
283 );
284 Ok(CHeapBox::from_ptr(dest_buffer))
285 }
286 }
287
288 fn length(&self) -> usize {
289 u32::from_be_bytes(self.total_length).saturating_sub(5) as usize
290 }
291
292 fn data_ptr(ptr: NonNull<Self>) -> *mut u8 {
293 unsafe {
294 ptr.as_ptr()
295 .cast::<u8>()
296 .byte_offset(offset_of!(Self, data) as isize)
297 }
298 }
299
300 unsafe fn data_slice<'a>(ptr: NonNull<Self>) -> &'a mut [u8] {
301 unsafe { slice::from_raw_parts_mut(Self::data_ptr(ptr), ptr.as_ref().length()) }
302 }
303
304 pub unsafe fn data<'a>(ptr: NonNull<Self>) -> (&'a [u8], u8) {
305 unsafe { (Self::data_slice(ptr), ptr.as_ref().data_type) }
306 }
307
308 pub unsafe fn zero_contents(ptr: NonNull<Self>) {
309 for byte in Self::data_slice(ptr) {
310 ptr::write_volatile(byte as *mut u8, mem::zeroed());
311 }
312 ptr::write_volatile(ptr.as_ptr(), mem::zeroed());
313 }
314
315 #[allow(clippy::wrong_self_convention)]
316 pub unsafe fn as_binary_data(ptr: NonNull<Self>) -> BinaryData {
317 let (data, data_type) = unsafe { (CBinaryData::data_slice(ptr), ptr.as_ref().data_type) };
318 (Vec::from(data), data_type).into()
319 }
320 }
321
322 #[cfg(test)] 261 #[cfg(test)]
323 mod tests { 262 mod tests {
324 use super::*; 263 use super::*;
325 use std::hint; 264 use std::cell::Cell;
326 #[test] 265 #[test]
327 fn test_box() { 266 fn test_box() {
328 #[allow(non_upper_case_globals)] 267 let drop_count: Cell<u32> = Cell::new(0);
329 static mut drop_count: u32 = 0; 268
330 269 struct Dropper<'a>(&'a Cell<u32>);
331 struct Dropper(i32); 270
332 271 impl Drop for Dropper<'_> {
333 impl Drop for Dropper {
334 fn drop(&mut self) { 272 fn drop(&mut self) {
335 unsafe { drop_count += 1 } 273 self.0.set(self.0.get() + 1)
336 } 274 }
337 } 275 }
338 276
339 let mut dropbox = CHeapBox::new(Dropper(9)).unwrap(); 277 let mut dropbox = CHeapBox::new(Dropper(&drop_count)).unwrap();
340 hint::black_box(dropbox.0); 278 _ = dropbox;
341 dropbox = CHeapBox::new(Dropper(10)).unwrap(); 279 // ensure the old value is dropped when the new one is assigned.
342 assert_eq!(1, unsafe { drop_count }); 280 dropbox = CHeapBox::new(Dropper(&drop_count)).unwrap();
343 hint::black_box(dropbox.0); 281 assert_eq!(1, drop_count.get());
282 *dropbox = Dropper(&drop_count);
283 assert_eq!(2, drop_count.get());
344 drop(dropbox); 284 drop(dropbox);
345 assert_eq!(2, unsafe { drop_count }); 285 assert_eq!(3, drop_count.get());
346 } 286 }
347 #[test] 287 #[test]
348 fn test_strings() { 288 fn test_strings() {
349 let str = CHeapString::new("hello there").unwrap(); 289 let str = CHeapString::new("hello there").unwrap();
350 let str_ptr = str.into_ptr().as_ptr(); 290 let str_ptr = str.into_ptr().as_ptr();