Mercurial > crates > nonstick
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(); |
