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 }