Mercurial > crates > nonstick
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 } |