Mercurial > crates > nonstick
view src/pam_ffi/memory.rs @ 72:47eb242a4f88
Fill out the PamHandle trait.
This updates the PamHandle trait to have methods for each Item,
and implements them on the LibPamHandle.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Wed, 04 Jun 2025 03:53:36 -0400 |
parents | 58f9d2a4df38 |
children | ac6881304c78 |
line wrap: on
line source
//! Things for dealing with memory. use crate::ErrorCode; use crate::Result; use std::ffi::{c_char, c_void, CStr, CString}; use std::marker::{PhantomData, PhantomPinned}; use std::result::Result as StdResult; use std::{ptr, slice}; /// Makes whatever it's in not [`Send`], [`Sync`], or [`Unpin`]. pub type Immovable = PhantomData<(*mut u8, PhantomPinned)>; /// Safely converts a `&str` option to a `CString` option. pub fn option_cstr(prompt: Option<&str>) -> Result<Option<CString>> { prompt .map(CString::new) .transpose() .map_err(|_| ErrorCode::ConversationError) } /// Gets the pointer to the given CString, or a null pointer if absent. pub fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { match prompt { Some(c_str) => c_str.as_ptr(), None => ptr::null(), } } /// Creates an owned copy of a string that is returned from a /// <code>pam_get_<var>whatever</var></code> function. /// /// # Safety /// /// It's on you to provide a valid string. pub unsafe fn copy_pam_string(result_ptr: *const libc::c_char) -> Result<String> { // We really shouldn't get a null pointer back here, but if we do, return nothing. if result_ptr.is_null() { return Ok(String::new()); } let bytes = unsafe { CStr::from_ptr(result_ptr) }; bytes .to_str() .map(String::from) .map_err(|_| ErrorCode::ConversationError) } /// Wraps a string returned from PAM as an `Option<&str>`. pub unsafe fn wrap_string<'a>(data: *const libc::c_char) -> Result<Option<&'a str>> { let ret = if data.is_null() { None } else { Some( CStr::from_ptr(data) .to_str() .map_err(|_| ErrorCode::ConversationError)?, ) }; Ok(ret) } /// Allocates a string with the given contents on the C heap. /// /// This is like [`CString::new`](std::ffi::CString::new), but: /// /// - it allocates data on the C heap with [`libc::malloc`]. /// - it doesn't take ownership of the data passed in. pub fn malloc_str(text: impl AsRef<str>) -> StdResult<*mut c_char, NulError> { let data = text.as_ref().as_bytes(); if let Some(nul) = data.iter().position(|x| *x == 0) { return Err(NulError(nul)); } unsafe { let data_alloc = libc::calloc(data.len() + 1, 1); libc::memcpy(data_alloc, data.as_ptr() as *const c_void, data.len()); Ok(data_alloc.cast()) } } /// Writes zeroes over the contents of a C string. /// /// This won't overwrite a null pointer. /// /// # Safety /// /// It's up to you to provide a valid C string. pub unsafe fn zero_c_string(cstr: *mut c_void) { if !cstr.is_null() { libc::memset(cstr, 0, libc::strlen(cstr as *const c_char)); } } /// Binary data used in requests and responses. /// /// This is an unsized data type whose memory goes beyond its data. /// This must be allocated on the C heap. /// /// A Linux-PAM extension. #[repr(C)] pub struct CBinaryData { /// The total length of the structure; a u32 in network byte order (BE). total_length: [u8; 4], /// A tag of undefined meaning. data_type: u8, /// Pointer to an array of length [`length`](Self::length) − 5 data: [u8; 0], _marker: Immovable, } impl CBinaryData { /// Copies the given data to a new BinaryData on the heap. pub fn alloc(source: &[u8], data_type: u8) -> StdResult<*mut CBinaryData, TooBigError> { let buffer_size = u32::try_from(source.len() + 5).map_err(|_| TooBigError { max: (u32::MAX - 5) as usize, actual: source.len(), })?; let data = unsafe { let dest_buffer = libc::malloc(buffer_size as usize) as *mut CBinaryData; let data = &mut *dest_buffer; data.total_length = buffer_size.to_be_bytes(); data.data_type = data_type; let dest = data.data.as_mut_ptr(); libc::memcpy( dest as *mut c_void, source.as_ptr() as *const c_void, source.len(), ); dest_buffer }; Ok(data) } fn length(&self) -> usize { u32::from_be_bytes(self.total_length).saturating_sub(5) as usize } pub fn contents(&self) -> &[u8] { unsafe { slice::from_raw_parts(self.data.as_ptr(), self.length()) } } pub fn data_type(&self) -> u8 { self.data_type } /// Clears this data and frees it. pub unsafe fn zero_contents(&mut self) { let contents = slice::from_raw_parts_mut(self.data.as_mut_ptr(), self.length()); for v in contents { *v = 0 } self.data_type = 0; self.total_length = [0; 4]; } } #[derive(Debug, thiserror::Error)] #[error("null byte within input at byte {0}")] pub struct NulError(pub usize); /// Returned when trying to fit too much data into a binary message. #[derive(Debug, thiserror::Error)] #[error("cannot create a message of {actual} bytes; maximum is {max}")] pub struct TooBigError { pub actual: usize, pub max: usize, } #[cfg(test)] mod tests { use super::{copy_pam_string, malloc_str, option_cstr, prompt_ptr, zero_c_string}; use crate::ErrorCode; use std::ffi::CString; #[test] fn test_strings() { let str = malloc_str("hello there").unwrap(); malloc_str("hell\0 there").unwrap_err(); unsafe { let copied = copy_pam_string(str.cast()).unwrap(); assert_eq!("hello there", copied); zero_c_string(str.cast()); let idx_three = str.add(3).as_mut().unwrap(); *idx_three = 0x80u8 as i8; let zeroed = copy_pam_string(str.cast()).unwrap(); assert!(zeroed.is_empty()); libc::free(str.cast()); } } #[test] fn test_option_str() { let good = option_cstr(Some("whatever")).unwrap(); assert_eq!("whatever", good.unwrap().to_str().unwrap()); let no_str = option_cstr(None).unwrap(); assert!(no_str.is_none()); let bad_str = option_cstr(Some("what\0ever")).unwrap_err(); assert_eq!(ErrorCode::ConversationError, bad_str); } #[test] fn test_prompt() { let prompt_cstr = CString::new("good").ok(); let prompt = prompt_ptr(prompt_cstr.as_ref()); assert!(!prompt.is_null()); let no_prompt = prompt_ptr(None); assert!(no_prompt.is_null()); } }