comparison src/libpam/memory.rs @ 98:b87100c5eed4

Start on environment variables, and make pointers nicer. This starts work on the PAM environment handling, and in so doing, introduces the CHeapBox and CHeapString structs. These are analogous to Box and CString, but they're located on the C heap rather than being Rust-managed memory. This is because environment variables deal with even more pointers and it turns out we can lose a lot of manual freeing using homemade smart pointers.
author Paul Fisher <paul@pfish.zone>
date Tue, 24 Jun 2025 04:25:25 -0400
parents 51c9d7e8261a
children 3f11b8d30f63
comparison
equal deleted inserted replaced
97:efe2f5f8b5b2 98:b87100c5eed4
1 //! Things for dealing with memory. 1 //! Things for dealing with memory.
2 2
3 use crate::Result; 3 use crate::Result;
4 use crate::{BinaryData, ErrorCode}; 4 use crate::{BinaryData, ErrorCode};
5 use std::error::Error;
5 use std::ffi::{c_char, CStr, CString}; 6 use std::ffi::{c_char, CStr, CString};
7 use std::fmt::{Display, Formatter, Result as FmtResult};
6 use std::marker::{PhantomData, PhantomPinned}; 8 use std::marker::{PhantomData, PhantomPinned};
7 use std::mem::offset_of; 9 use std::mem::offset_of;
10 use std::ops::{Deref, DerefMut};
8 use std::ptr::NonNull; 11 use std::ptr::NonNull;
12 use std::result::Result as StdResult;
9 use std::{mem, ptr, slice}; 13 use std::{mem, ptr, slice};
14
15 /// Raised from `calloc` when you have no memory!
16 #[derive(Debug)]
17 pub struct NoMem;
18
19 impl Display for NoMem {
20 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
21 write!(f, "out of memory!")
22 }
23 }
24
25 impl Error for NoMem {}
26
27 impl From<NoMem> for ErrorCode {
28 fn from(_: NoMem) -> Self {
29 ErrorCode::BufferError
30 }
31 }
10 32
11 /// Allocates `count` elements to hold `T`. 33 /// Allocates `count` elements to hold `T`.
12 #[inline] 34 #[inline]
13 pub fn calloc<T>(count: usize) -> *mut T { 35 pub fn calloc<T>(count: usize) -> StdResult<NonNull<T>, NoMem> {
14 // SAFETY: it's always safe to allocate! Leaking memory is fun! 36 // SAFETY: it's always safe to allocate! Leaking memory is fun!
15 unsafe { libc::calloc(count, size_of::<T>()) }.cast() 37 NonNull::new(unsafe { libc::calloc(count, size_of::<T>()) }.cast()).ok_or(NoMem)
16 } 38 }
17 39
18 /// Wrapper for [`libc::free`] to make debugging calls/frees easier. 40 /// Wrapper for [`libc::free`] to make debugging calls/frees easier.
19 /// 41 ///
20 /// # Safety 42 /// # Safety
41 /// Gets the pointer to the given CString, or a null pointer if absent. 63 /// Gets the pointer to the given CString, or a null pointer if absent.
42 pub fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { 64 pub fn prompt_ptr(prompt: Option<&CString>) -> *const c_char {
43 match prompt { 65 match prompt {
44 Some(c_str) => c_str.as_ptr(), 66 Some(c_str) => c_str.as_ptr(),
45 None => ptr::null(), 67 None => ptr::null(),
68 }
69 }
70
71 /// It's like a [`Box`], but C heap managed.
72 #[derive(Debug)]
73 #[repr(transparent)]
74 pub struct CHeapBox<T>(NonNull<T>);
75
76 // Lots of "as" and "into" associated functions.
77 #[allow(clippy::wrong_self_convention)]
78 impl<T> CHeapBox<T> {
79 /// Creates a new CHeapBox holding the given data.
80 pub fn new(value: T) -> Result<Self> {
81 let memory = calloc(1)?;
82 unsafe { ptr::write(memory.as_ptr(), value) }
83 // SAFETY: We literally just allocated this.
84 Ok(Self(memory))
85 }
86
87 /// Takes ownership of the given pointer.
88 ///
89 /// # Safety
90 ///
91 /// You have to provide a valid pointer to the start of an allocation
92 /// that was made with `malloc`.
93 pub unsafe fn from_ptr(ptr: NonNull<T>) -> Self {
94 Self(ptr)
95 }
96
97 /// Converts this CBox into a raw pointer.
98 pub fn into_ptr(this: Self) -> NonNull<T> {
99 let ret = this.0;
100 mem::forget(this);
101 ret
102 }
103
104 /// Gets a pointer from this but doesn't convert this into a raw pointer.
105 ///
106 /// You are responsible for ensuring the CHeapBox lives long enough.
107 pub fn as_ptr(this: &Self) -> NonNull<T> {
108 this.0
109 }
110
111 /// Converts this into a Box of a different type.
112 ///
113 /// # Safety
114 ///
115 /// The different type has to be compatible in size/alignment and drop behavior.
116 pub unsafe fn cast<R>(this: Self) -> CHeapBox<R> {
117 mem::transmute(this)
118 }
119 }
120
121 impl<T: Default> Default for CHeapBox<T> {
122 fn default() -> Self {
123 Self::new(Default::default()).expect("allocation should not fail")
124 }
125 }
126
127 impl<T> Deref for CHeapBox<T> {
128 type Target = T;
129 fn deref(&self) -> &Self::Target {
130 // SAFETY: We own this pointer and it is guaranteed valid.
131 unsafe { Self::as_ptr(self).as_ref() }
132 }
133 }
134
135 impl<T> DerefMut for CHeapBox<T> {
136 fn deref_mut(&mut self) -> &mut Self::Target {
137 // SAFETY: We own this pointer and it is guaranteed valid.
138 unsafe { Self::as_ptr(self).as_mut() }
139 }
140 }
141
142 impl<T> Drop for CHeapBox<T> {
143 fn drop(&mut self) {
144 // SAFETY: We own a valid pointer, and will never use it after this.
145 unsafe {
146 let ptr = self.0.as_ptr();
147 ptr::drop_in_place(ptr);
148 free(ptr)
149 }
150 }
151 }
152
153 /// A null-terminated string allocated on the C heap.
154 ///
155 /// Basically [`CString`], but managed by malloc.
156 #[derive(Debug)]
157 #[repr(transparent)]
158 pub struct CHeapString(CHeapBox<c_char>);
159
160 impl CHeapString {
161 /// Creates a new C heap string with the given contents.
162 pub fn new(text: &str) -> Result<Self> {
163 let data = text.as_bytes();
164 if data.contains(&0) {
165 return Err(ErrorCode::ConversationError);
166 }
167 // +1 for the null terminator
168 let data_alloc: NonNull<c_char> = calloc(data.len() + 1)?;
169 // SAFETY: we just allocated this and we have enough room.
170 unsafe {
171 libc::memcpy(data_alloc.as_ptr().cast(), data.as_ptr().cast(), data.len());
172 Ok(Self(CHeapBox::from_ptr(data_alloc)))
173 }
174 }
175
176 /// Converts this C heap string into a raw pointer.
177 ///
178 /// You are responsible for freeing it later.
179 pub fn into_ptr(self) -> NonNull<c_char> {
180 let ptr = CHeapBox::as_ptr(&self.0);
181 mem::forget(self);
182 ptr
183 }
184
185 /// Converts this into a dumb box. It will no longer be zeroed upon drop.
186 pub fn into_box(self) -> CHeapBox<c_char> {
187 unsafe { mem::transmute(self) }
188 }
189
190 /// Takes ownership of a C heap string.
191 ///
192 /// # Safety
193 ///
194 /// You have to provide a pointer to the start of an allocation that is
195 /// a valid 0-terminated C string.
196 unsafe fn from_ptr(ptr: *mut c_char) -> Option<Self> {
197 NonNull::new(ptr).map(|p| unsafe { Self(CHeapBox::from_ptr(p)) })
198 }
199
200 unsafe fn from_box<T>(bx: CHeapBox<T>) -> Self {
201 Self(CHeapBox::cast(bx))
202 }
203
204 /// Zeroes the contents of a C string.
205 ///
206 /// # Safety
207 ///
208 /// You have to provide a valid pointer to a null-terminated C string.
209 pub unsafe fn zero(ptr: NonNull<c_char>) {
210 let cstr = ptr.as_ptr();
211 let len = libc::strlen(cstr.cast());
212 for x in 0..len {
213 ptr::write_volatile(cstr.byte_offset(x as isize), mem::zeroed())
214 }
215 }
216 }
217
218 impl Drop for CHeapString {
219 fn drop(&mut self) {
220 // SAFETY: We own a valid C String
221 unsafe { Self::zero(CHeapBox::as_ptr(&self.0)) }
222 }
223 }
224
225 impl Deref for CHeapString {
226 type Target = CStr;
227
228 fn deref(&self) -> &Self::Target {
229 // SAFETY: We know we own a valid C string pointer.
230 let ptr = CHeapBox::as_ptr(&self.0).as_ptr();
231 unsafe { CStr::from_ptr(ptr) }
46 } 232 }
47 } 233 }
48 234
49 /// Creates an owned copy of a string that is returned from a 235 /// Creates an owned copy of a string that is returned from a
50 /// <code>pam_get_<var>whatever</var></code> function. 236 /// <code>pam_get_<var>whatever</var></code> function.
62 None => return Ok(None), 248 None => return Ok(None),
63 }; 249 };
64 Ok(borrowed.map(String::from)) 250 Ok(borrowed.map(String::from))
65 } 251 }
66 252
67 /// Allocates a string with the given contents on the C heap.
68 ///
69 /// This is like [`CString::new`], but:
70 ///
71 /// - it allocates data on the C heap with [`libc::malloc`].
72 /// - it doesn't take ownership of the data passed in.
73 pub fn malloc_str(text: &str) -> Result<NonNull<c_char>> {
74 let data = text.as_bytes();
75 if data.contains(&0) {
76 return Err(ErrorCode::ConversationError);
77 }
78 // +1 for the null terminator
79 let data_alloc: *mut c_char = calloc(data.len() + 1);
80 // SAFETY: we just allocated this and we have enough room.
81 unsafe {
82 libc::memcpy(data_alloc.cast(), data.as_ptr().cast(), data.len());
83 Ok(NonNull::new_unchecked(data_alloc))
84 }
85 }
86
87 /// Writes zeroes over the contents of a C string.
88 ///
89 /// This won't overwrite a null pointer.
90 ///
91 /// # Safety
92 ///
93 /// It's up to you to provide a valid C string.
94 pub unsafe fn zero_c_string(cstr: *mut c_char) {
95 if !cstr.is_null() {
96 let len = libc::strlen(cstr.cast());
97 for x in 0..len {
98 ptr::write_volatile(cstr.byte_offset(x as isize), mem::zeroed())
99 }
100 }
101 }
102
103 /// Binary data used in requests and responses. 253 /// Binary data used in requests and responses.
104 /// 254 ///
105 /// This is an unsized data type whose memory goes beyond its data. 255 /// This is an unsized data type whose memory goes beyond its data.
106 /// This must be allocated on the C heap. 256 /// This must be allocated on the C heap.
107 /// 257 ///
117 _marker: Immovable, 267 _marker: Immovable,
118 } 268 }
119 269
120 impl CBinaryData { 270 impl CBinaryData {
121 /// Copies the given data to a new BinaryData on the heap. 271 /// Copies the given data to a new BinaryData on the heap.
122 pub fn alloc((data, data_type): (&[u8], u8)) -> Result<NonNull<CBinaryData>> { 272 pub fn alloc((data, data_type): (&[u8], u8)) -> Result<CHeapBox<CBinaryData>> {
123 let buffer_size = 273 let buffer_size =
124 u32::try_from(data.len() + 5).map_err(|_| ErrorCode::ConversationError)?; 274 u32::try_from(data.len() + 5).map_err(|_| ErrorCode::ConversationError)?;
125 // SAFETY: We're only allocating here. 275 // SAFETY: We're only allocating here.
126 let dest = unsafe { 276 unsafe {
127 let mut dest_buffer: NonNull<Self> = 277 let mut dest_buffer: NonNull<Self> = calloc::<u8>(buffer_size as usize)?.cast();
128 NonNull::new_unchecked(calloc::<u8>(buffer_size as usize).cast());
129 let dest = dest_buffer.as_mut(); 278 let dest = dest_buffer.as_mut();
130 dest.total_length = buffer_size.to_be_bytes(); 279 dest.total_length = buffer_size.to_be_bytes();
131 dest.data_type = data_type; 280 dest.data_type = data_type;
132 libc::memcpy( 281 libc::memcpy(
133 Self::data_ptr(dest_buffer).cast(), 282 Self::data_ptr(dest_buffer).cast(),
134 data.as_ptr().cast(), 283 data.as_ptr().cast(),
135 data.len(), 284 data.len(),
136 ); 285 );
137 dest_buffer 286 Ok(CHeapBox::from_ptr(dest_buffer))
138 }; 287 }
139 Ok(dest)
140 } 288 }
141 289
142 fn length(&self) -> usize { 290 fn length(&self) -> usize {
143 u32::from_be_bytes(self.total_length).saturating_sub(5) as usize 291 u32::from_be_bytes(self.total_length).saturating_sub(5) as usize
144 } 292 }
173 } 321 }
174 } 322 }
175 323
176 #[cfg(test)] 324 #[cfg(test)]
177 mod tests { 325 mod tests {
178 use super::{ 326 use super::*;
179 copy_pam_string, free, malloc_str, option_cstr, prompt_ptr, zero_c_string, CString, 327 use std::hint;
180 ErrorCode, 328 #[test]
181 }; 329 fn test_box() {
330 #[allow(non_upper_case_globals)]
331 static mut drop_count: u32 = 0;
332
333 struct Dropper(i32);
334
335 impl Drop for Dropper {
336 fn drop(&mut self) {
337 unsafe { drop_count += 1 }
338 }
339 }
340
341 let mut dropbox = CHeapBox::new(Dropper(9)).unwrap();
342 hint::black_box(dropbox.0);
343 dropbox = CHeapBox::new(Dropper(10)).unwrap();
344 assert_eq!(1, unsafe { drop_count });
345 hint::black_box(dropbox.0);
346 drop(dropbox);
347 assert_eq!(2, unsafe { drop_count });
348 }
182 #[test] 349 #[test]
183 fn test_strings() { 350 fn test_strings() {
184 let str = malloc_str("hello there").unwrap(); 351 let str = CHeapString::new("hello there").unwrap();
185 let str = str.as_ptr(); 352 let str_ptr = str.into_ptr().as_ptr();
186 malloc_str("hell\0 there").unwrap_err(); 353 CHeapString::new("hell\0 there").unwrap_err();
187 unsafe { 354 unsafe {
188 let copied = copy_pam_string(str).unwrap(); 355 let copied = copy_pam_string(str_ptr).unwrap();
189 assert_eq!("hello there", copied.unwrap()); 356 assert_eq!("hello there", copied.unwrap());
190 zero_c_string(str); 357 CHeapString::zero(NonNull::new(str_ptr).unwrap());
191 let idx_three = str.add(3).as_mut().unwrap(); 358 let idx_three = str_ptr.add(3).as_mut().unwrap();
192 *idx_three = 0x80u8 as i8; 359 *idx_three = 0x80u8 as i8;
193 let zeroed = copy_pam_string(str).unwrap().unwrap(); 360 let zeroed = copy_pam_string(str_ptr).unwrap().unwrap();
194 assert!(zeroed.is_empty()); 361 assert!(zeroed.is_empty());
195 free(str); 362 let _ = CHeapString::from_ptr(str_ptr);
196 } 363 }
197 } 364 }
198 365
199 #[test] 366 #[test]
200 fn test_option_str() { 367 fn test_option_str() {