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