Mercurial > crates > nonstick
comparison src/libpam/memory.rs @ 75:c30811b4afae
rename pam_ffi submodule to libpam.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Fri, 06 Jun 2025 22:35:08 -0400 |
| parents | src/pam_ffi/memory.rs@ac6881304c78 |
| children | 351bdc13005e |
comparison
equal
deleted
inserted
replaced
| 74:c7c596e6388f | 75:c30811b4afae |
|---|---|
| 1 //! Things for dealing with memory. | |
| 2 | |
| 3 use crate::ErrorCode; | |
| 4 use crate::Result; | |
| 5 use std::ffi::{c_char, c_void, CStr, CString}; | |
| 6 use std::marker::{PhantomData, PhantomPinned}; | |
| 7 use std::result::Result as StdResult; | |
| 8 use std::{ptr, slice}; | |
| 9 | |
| 10 /// Makes whatever it's in not [`Send`], [`Sync`], or [`Unpin`]. | |
| 11 #[repr(C)] | |
| 12 #[derive(Debug)] | |
| 13 pub struct Immovable(pub PhantomData<(*mut u8, PhantomPinned)>); | |
| 14 | |
| 15 /// Safely converts a `&str` option to a `CString` option. | |
| 16 pub fn option_cstr(prompt: Option<&str>) -> Result<Option<CString>> { | |
| 17 prompt | |
| 18 .map(CString::new) | |
| 19 .transpose() | |
| 20 .map_err(|_| ErrorCode::ConversationError) | |
| 21 } | |
| 22 | |
| 23 /// Gets the pointer to the given CString, or a null pointer if absent. | |
| 24 pub fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { | |
| 25 match prompt { | |
| 26 Some(c_str) => c_str.as_ptr(), | |
| 27 None => ptr::null(), | |
| 28 } | |
| 29 } | |
| 30 | |
| 31 /// Creates an owned copy of a string that is returned from a | |
| 32 /// <code>pam_get_<var>whatever</var></code> function. | |
| 33 /// | |
| 34 /// # Safety | |
| 35 /// | |
| 36 /// It's on you to provide a valid string. | |
| 37 pub unsafe fn copy_pam_string(result_ptr: *const libc::c_char) -> Result<String> { | |
| 38 // We really shouldn't get a null pointer back here, but if we do, return nothing. | |
| 39 if result_ptr.is_null() { | |
| 40 return Ok(String::new()); | |
| 41 } | |
| 42 let bytes = unsafe { CStr::from_ptr(result_ptr) }; | |
| 43 bytes | |
| 44 .to_str() | |
| 45 .map(String::from) | |
| 46 .map_err(|_| ErrorCode::ConversationError) | |
| 47 } | |
| 48 | |
| 49 /// Wraps a string returned from PAM as an `Option<&str>`. | |
| 50 pub unsafe fn wrap_string<'a>(data: *const libc::c_char) -> Result<Option<&'a str>> { | |
| 51 let ret = if data.is_null() { | |
| 52 None | |
| 53 } else { | |
| 54 Some( | |
| 55 CStr::from_ptr(data) | |
| 56 .to_str() | |
| 57 .map_err(|_| ErrorCode::ConversationError)?, | |
| 58 ) | |
| 59 }; | |
| 60 Ok(ret) | |
| 61 } | |
| 62 | |
| 63 /// Allocates a string with the given contents on the C heap. | |
| 64 /// | |
| 65 /// This is like [`CString::new`](std::ffi::CString::new), but: | |
| 66 /// | |
| 67 /// - it allocates data on the C heap with [`libc::malloc`]. | |
| 68 /// - it doesn't take ownership of the data passed in. | |
| 69 pub fn malloc_str(text: impl AsRef<str>) -> StdResult<*mut c_char, NulError> { | |
| 70 let data = text.as_ref().as_bytes(); | |
| 71 if let Some(nul) = data.iter().position(|x| *x == 0) { | |
| 72 return Err(NulError(nul)); | |
| 73 } | |
| 74 unsafe { | |
| 75 let data_alloc = libc::calloc(data.len() + 1, 1); | |
| 76 libc::memcpy(data_alloc, data.as_ptr().cast(), data.len()); | |
| 77 Ok(data_alloc.cast()) | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 /// Writes zeroes over the contents of a C string. | |
| 82 /// | |
| 83 /// This won't overwrite a null pointer. | |
| 84 /// | |
| 85 /// # Safety | |
| 86 /// | |
| 87 /// It's up to you to provide a valid C string. | |
| 88 pub unsafe fn zero_c_string(cstr: *mut c_void) { | |
| 89 if !cstr.is_null() { | |
| 90 libc::memset(cstr, 0, libc::strlen(cstr.cast())); | |
| 91 } | |
| 92 } | |
| 93 | |
| 94 /// Binary data used in requests and responses. | |
| 95 /// | |
| 96 /// This is an unsized data type whose memory goes beyond its data. | |
| 97 /// This must be allocated on the C heap. | |
| 98 /// | |
| 99 /// A Linux-PAM extension. | |
| 100 #[repr(C)] | |
| 101 pub struct CBinaryData { | |
| 102 /// The total length of the structure; a u32 in network byte order (BE). | |
| 103 total_length: [u8; 4], | |
| 104 /// A tag of undefined meaning. | |
| 105 data_type: u8, | |
| 106 /// Pointer to an array of length [`length`](Self::length) − 5 | |
| 107 data: [u8; 0], | |
| 108 _marker: Immovable, | |
| 109 } | |
| 110 | |
| 111 impl CBinaryData { | |
| 112 /// Copies the given data to a new BinaryData on the heap. | |
| 113 pub fn alloc(source: &[u8], data_type: u8) -> StdResult<*mut CBinaryData, TooBigError> { | |
| 114 let buffer_size = u32::try_from(source.len() + 5).map_err(|_| TooBigError { | |
| 115 max: (u32::MAX - 5) as usize, | |
| 116 actual: source.len(), | |
| 117 })?; | |
| 118 // SAFETY: We're only allocating here. | |
| 119 let data = unsafe { | |
| 120 let dest_buffer: *mut CBinaryData = libc::malloc(buffer_size as usize).cast(); | |
| 121 let data = &mut *dest_buffer; | |
| 122 data.total_length = buffer_size.to_be_bytes(); | |
| 123 data.data_type = data_type; | |
| 124 let dest = data.data.as_mut_ptr(); | |
| 125 libc::memcpy(dest.cast(), source.as_ptr().cast(), source.len()); | |
| 126 dest_buffer | |
| 127 }; | |
| 128 Ok(data) | |
| 129 } | |
| 130 | |
| 131 fn length(&self) -> usize { | |
| 132 u32::from_be_bytes(self.total_length).saturating_sub(5) as usize | |
| 133 } | |
| 134 | |
| 135 pub fn contents(&self) -> &[u8] { | |
| 136 unsafe { slice::from_raw_parts(self.data.as_ptr(), self.length()) } | |
| 137 } | |
| 138 pub fn data_type(&self) -> u8 { | |
| 139 self.data_type | |
| 140 } | |
| 141 | |
| 142 /// Clears this data and frees it. | |
| 143 pub unsafe fn zero_contents(&mut self) { | |
| 144 let contents = slice::from_raw_parts_mut(self.data.as_mut_ptr(), self.length()); | |
| 145 for v in contents { | |
| 146 *v = 0 | |
| 147 } | |
| 148 self.data_type = 0; | |
| 149 self.total_length = [0; 4]; | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 #[derive(Debug, thiserror::Error)] | |
| 154 #[error("null byte within input at byte {0}")] | |
| 155 pub struct NulError(pub usize); | |
| 156 | |
| 157 /// Returned when trying to fit too much data into a binary message. | |
| 158 #[derive(Debug, thiserror::Error)] | |
| 159 #[error("cannot create a message of {actual} bytes; maximum is {max}")] | |
| 160 pub struct TooBigError { | |
| 161 pub actual: usize, | |
| 162 pub max: usize, | |
| 163 } | |
| 164 | |
| 165 #[cfg(test)] | |
| 166 mod tests { | |
| 167 use super::{copy_pam_string, malloc_str, option_cstr, prompt_ptr, zero_c_string}; | |
| 168 use crate::ErrorCode; | |
| 169 use std::ffi::CString; | |
| 170 #[test] | |
| 171 fn test_strings() { | |
| 172 let str = malloc_str("hello there").unwrap(); | |
| 173 malloc_str("hell\0 there").unwrap_err(); | |
| 174 unsafe { | |
| 175 let copied = copy_pam_string(str.cast()).unwrap(); | |
| 176 assert_eq!("hello there", copied); | |
| 177 zero_c_string(str.cast()); | |
| 178 let idx_three = str.add(3).as_mut().unwrap(); | |
| 179 *idx_three = 0x80u8 as i8; | |
| 180 let zeroed = copy_pam_string(str.cast()).unwrap(); | |
| 181 assert!(zeroed.is_empty()); | |
| 182 libc::free(str.cast()); | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 #[test] | |
| 187 fn test_option_str() { | |
| 188 let good = option_cstr(Some("whatever")).unwrap(); | |
| 189 assert_eq!("whatever", good.unwrap().to_str().unwrap()); | |
| 190 let no_str = option_cstr(None).unwrap(); | |
| 191 assert!(no_str.is_none()); | |
| 192 let bad_str = option_cstr(Some("what\0ever")).unwrap_err(); | |
| 193 assert_eq!(ErrorCode::ConversationError, bad_str); | |
| 194 } | |
| 195 | |
| 196 #[test] | |
| 197 fn test_prompt() { | |
| 198 let prompt_cstr = CString::new("good").ok(); | |
| 199 let prompt = prompt_ptr(prompt_cstr.as_ref()); | |
| 200 assert!(!prompt.is_null()); | |
| 201 let no_prompt = prompt_ptr(None); | |
| 202 assert!(no_prompt.is_null()); | |
| 203 } | |
| 204 } |
