comparison src/pam_ffi/memory.rs @ 71:58f9d2a4df38

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