comparison src/libpam/answer.rs @ 98:b87100c5eed4

Start on environment variables, and make pointers nicer. This starts work on the PAM environment handling, and in so doing, introduces the CHeapBox and CHeapString structs. These are analogous to Box and CString, but they're located on the C heap rather than being Rust-managed memory. This is because environment variables deal with even more pointers and it turns out we can lose a lot of manual freeing using homemade smart pointers.
author Paul Fisher <paul@pfish.zone>
date Tue, 24 Jun 2025 04:25:25 -0400
parents efc2b56c8928
children 3f11b8d30f63
comparison
equal deleted inserted replaced
97:efe2f5f8b5b2 98:b87100c5eed4
1 //! Types used to communicate data from the application to the module. 1 //! Types used to communicate data from the application to the module.
2 2
3 use crate::libpam::conversation::OwnedMessage; 3 use crate::libpam::conversation::OwnedMessage;
4 use crate::libpam::memory; 4 use crate::libpam::memory;
5 use crate::libpam::memory::CBinaryData; 5 use crate::libpam::memory::{CBinaryData, CHeapBox, CHeapString};
6 pub use crate::libpam::pam_ffi::Answer; 6 pub use crate::libpam::pam_ffi::Answer;
7 use crate::{ErrorCode, Result}; 7 use crate::{ErrorCode, Result};
8 use std::ffi::CStr; 8 use std::ffi::CStr;
9 use std::ops::{Deref, DerefMut}; 9 use std::ops::{Deref, DerefMut};
10 use std::ptr::NonNull; 10 use std::ptr::NonNull;
11 use std::{iter, mem, ptr, slice}; 11 use std::{iter, mem, ptr, slice};
12 12
13 /// The corridor via which the answer to Messages navigate through PAM. 13 /// The corridor via which the answer to Messages navigate through PAM.
14 #[derive(Debug)] 14 #[derive(Debug)]
15 pub struct Answers { 15 pub struct Answers {
16 base: *mut Answer, 16 /// The actual list of answers. This can't be a [`CHeapBox`] because
17 /// this is the pointer to the start of an array, not a single Answer.
18 base: NonNull<Answer>,
17 count: usize, 19 count: usize,
18 } 20 }
19 21
20 impl Answers { 22 impl Answers {
21 /// Builds an Answers out of the given answered Message Q&As. 23 /// Builds an Answers out of the given answered Message Q&As.
22 pub fn build(value: Vec<OwnedMessage>) -> Result<Self> { 24 pub fn build(value: Vec<OwnedMessage>) -> Result<Self> {
23 let mut outputs = Self { 25 let mut outputs = Self {
24 base: memory::calloc(value.len()), 26 base: memory::calloc(value.len())?,
25 count: value.len(), 27 count: value.len(),
26 }; 28 };
27 // Even if we fail during this process, we still end up freeing 29 // Even if we fail during this process, we still end up freeing
28 // all allocated answer memory. 30 // all allocated answer memory.
29 for (input, output) in iter::zip(value, outputs.iter_mut()) { 31 for (input, output) in iter::zip(value, outputs.iter_mut()) {
43 45
44 /// Converts this into a `*Answer` for passing to PAM. 46 /// Converts this into a `*Answer` for passing to PAM.
45 /// 47 ///
46 /// This object is consumed and the `Answer` pointer now owns its data. 48 /// This object is consumed and the `Answer` pointer now owns its data.
47 /// It can be recreated with [`Self::from_c_heap`]. 49 /// It can be recreated with [`Self::from_c_heap`].
48 pub fn into_ptr(self) -> *mut Answer { 50 pub fn into_ptr(self) -> NonNull<Answer> {
49 let ret = self.base; 51 let ret = self.base;
50 mem::forget(self); 52 mem::forget(self);
51 ret 53 ret
52 } 54 }
53 55
55 /// 57 ///
56 /// # Safety 58 /// # Safety
57 /// 59 ///
58 /// It's up to you to make sure you pass a valid pointer, 60 /// It's up to you to make sure you pass a valid pointer,
59 /// like one that you got from PAM, or maybe [`Self::into_ptr`]. 61 /// like one that you got from PAM, or maybe [`Self::into_ptr`].
60 pub unsafe fn from_c_heap(base: *mut Answer, count: usize) -> Self { 62 pub unsafe fn from_c_heap(base: NonNull<Answer>, count: usize) -> Self {
61 Answers { base, count } 63 Answers { base, count }
62 } 64 }
63 } 65 }
64 66
65 impl Deref for Answers { 67 impl Deref for Answers {
66 type Target = [Answer]; 68 type Target = [Answer];
67 fn deref(&self) -> &Self::Target { 69 fn deref(&self) -> &Self::Target {
68 // SAFETY: This is the memory we manage ourselves. 70 // SAFETY: This is the memory we manage ourselves.
69 unsafe { slice::from_raw_parts(self.base, self.count) } 71 unsafe { slice::from_raw_parts(self.base.as_ptr(), self.count) }
70 } 72 }
71 } 73 }
72 74
73 impl DerefMut for Answers { 75 impl DerefMut for Answers {
74 fn deref_mut(&mut self) -> &mut Self::Target { 76 fn deref_mut(&mut self) -> &mut Self::Target {
75 // SAFETY: This is the memory we manage ourselves. 77 // SAFETY: This is the memory we manage ourselves.
76 unsafe { slice::from_raw_parts_mut(self.base, self.count) } 78 unsafe { slice::from_raw_parts_mut(self.base.as_ptr(), self.count) }
77 } 79 }
78 } 80 }
79 81
80 impl Drop for Answers { 82 impl Drop for Answers {
81 fn drop(&mut self) { 83 fn drop(&mut self) {
82 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. 84 // SAFETY: We allocated this ourselves, or it was provided to us by PAM.
85 // We own these pointers, and they will never be used after this.
83 unsafe { 86 unsafe {
84 for answer in self.iter_mut() { 87 for answer in self.iter_mut() {
85 answer.free_contents() 88 ptr::drop_in_place(answer)
86 } 89 }
87 memory::free(self.base) 90 memory::free(self.base.as_ptr())
88 } 91 }
89 } 92 }
90 } 93 }
91 94
92 #[repr(transparent)] 95 #[repr(transparent)]
104 &mut *(from as *mut Answer).cast::<Self>() 107 &mut *(from as *mut Answer).cast::<Self>()
105 } 108 }
106 109
107 /// Converts the `Answer` to a `TextAnswer` with the given text. 110 /// Converts the `Answer` to a `TextAnswer` with the given text.
108 fn fill(dest: &mut Answer, text: &str) -> Result<()> { 111 fn fill(dest: &mut Answer, text: &str) -> Result<()> {
109 let allocated = memory::malloc_str(text)?; 112 let allocated = CHeapString::new(text)?;
110 dest.free_contents(); 113 let _ = dest
111 dest.data = allocated.as_ptr().cast(); 114 .data
115 .replace(unsafe { CHeapBox::cast(allocated.into_box()) });
112 Ok(()) 116 Ok(())
113 } 117 }
114 118
115 /// Gets the string stored in this answer. 119 /// Gets the string stored in this answer.
116 pub fn contents(&self) -> Result<&str> { 120 pub fn contents(&self) -> Result<&str> {
117 if self.0.data.is_null() { 121 match self.0.data.as_ref() {
118 Ok("") 122 None => Ok(""),
119 } else { 123 Some(data) => {
120 // SAFETY: This data is either passed from PAM (so we are forced 124 // SAFETY: This data is either passed from PAM (so we are forced
121 // to trust it) or was created by us in TextAnswerInner::alloc. 125 // to trust it) or was created by us in TextAnswerInner::alloc.
122 // In either case, it's going to be a valid null-terminated string. 126 // In either case, it's going to be a valid null-terminated string.
123 unsafe { CStr::from_ptr(self.0.data.cast()) } 127 unsafe { CStr::from_ptr(CHeapBox::as_ptr(data).as_ptr().cast()) }
124 .to_str() 128 .to_str()
125 .map_err(|_| ErrorCode::ConversationError) 129 .map_err(|_| ErrorCode::ConversationError)
130 }
126 } 131 }
127 } 132 }
128 133
129 /// Zeroes out the answer data, frees it, and points our data to `null`. 134 /// Zeroes out the answer data, frees it, and points our data to `null`.
130 /// 135 ///
131 /// When this `TextAnswer` is part of an [`Answers`], 136 /// When this `TextAnswer` is part of an [`Answers`],
132 /// this is optional (since that will perform the `free`), 137 /// this is optional (since that will perform the `free`),
133 /// but it will clear potentially sensitive data. 138 /// but it will clear potentially sensitive data.
134 pub fn free_contents(&mut self) { 139 pub fn zero_contents(&mut self) {
135 // SAFETY: We own this data and know it's valid. 140 // SAFETY: We own this data and know it's valid.
136 // If it's null, this is a no-op. 141 // If it's null, this is a no-op.
137 // After we're done, it will be null. 142 // After we're done, it will be null.
138 unsafe { 143 unsafe {
139 memory::zero_c_string(self.0.data.cast()); 144 if let Some(ptr) = self.0.data.as_ref() {
140 memory::free(self.0.data); 145 CHeapString::zero(CHeapBox::as_ptr(ptr).cast());
141 self.0.data = ptr::null_mut() 146 }
142 } 147 }
143 } 148 }
144 } 149 }
145 150
146 /// A [`Answer`] with [`CBinaryData`] in it. 151 /// A [`Answer`] with [`CBinaryData`] in it.
166 /// 171 ///
167 /// The referenced data is copied to the C heap. 172 /// The referenced data is copied to the C heap.
168 /// We do not take ownership of the original data. 173 /// We do not take ownership of the original data.
169 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { 174 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> {
170 let allocated = CBinaryData::alloc(data_and_type)?; 175 let allocated = CBinaryData::alloc(data_and_type)?;
171 dest.free_contents(); 176 let _ = dest.data.replace(unsafe { CHeapBox::cast(allocated) });
172 dest.data = allocated.as_ptr().cast();
173 Ok(()) 177 Ok(())
174 } 178 }
175 179
176 /// Gets the binary data in this answer. 180 /// Gets the binary data in this answer.
177 pub fn data(&self) -> Option<NonNull<CBinaryData>> { 181 pub fn data(&self) -> Option<NonNull<CBinaryData>> {
178 // SAFETY: We either got this data from PAM or allocated it ourselves. 182 // SAFETY: We either got this data from PAM or allocated it ourselves.
179 // Either way, we trust that it is either valid data or null. 183 // Either way, we trust that it is either valid data or null.
180 NonNull::new(self.0.data.cast::<CBinaryData>()) 184 self.0
185 .data
186 .as_ref()
187 .map(CHeapBox::as_ptr)
188 .map(NonNull::cast)
181 } 189 }
182 190
183 /// Zeroes out the answer data, frees it, and points our data to `null`. 191 /// Zeroes out the answer data, frees it, and points our data to `null`.
184 /// 192 ///
185 /// When this `BinaryAnswer` is part of an [`Answers`], 193 /// When this `BinaryAnswer` is part of an [`Answers`],
187 /// but it will clear potentially sensitive data. 195 /// but it will clear potentially sensitive data.
188 pub fn zero_contents(&mut self) { 196 pub fn zero_contents(&mut self) {
189 // SAFETY: We know that our data pointer is either valid or null. 197 // SAFETY: We know that our data pointer is either valid or null.
190 // Once we're done, it's null and the answer is safe. 198 // Once we're done, it's null and the answer is safe.
191 unsafe { 199 unsafe {
192 if let Some(ptr) = NonNull::new(self.0.data) { 200 if let Some(ptr) = self.0.data.as_ref() {
193 CBinaryData::zero_contents(ptr.cast()) 201 CBinaryData::zero_contents(CHeapBox::as_ptr(ptr).cast())
194 } 202 }
195 memory::free(self.0.data);
196 self.0.data = ptr::null_mut()
197 }
198 }
199 }
200
201 impl Answer {
202 /// Frees the contents of this answer.
203 ///
204 /// After this is done, this answer's `data` will be `null`,
205 /// which is a valid (empty) state.
206 fn free_contents(&mut self) {
207 // SAFETY: We have either an owned valid pointer, or null.
208 // We can free our owned pointer, and `free(null)` is a no-op.
209 unsafe {
210 memory::free(self.data);
211 self.data = ptr::null_mut();
212 } 203 }
213 } 204 }
214 } 205 }
215 206
216 #[cfg(test)] 207 #[cfg(test)]
227 } 218 }
228 219
229 fn assert_text_answer(want: &str, answer: &mut Answer) { 220 fn assert_text_answer(want: &str, answer: &mut Answer) {
230 let up = unsafe { TextAnswer::upcast(answer) }; 221 let up = unsafe { TextAnswer::upcast(answer) };
231 assert_eq!(want, up.contents().unwrap()); 222 assert_eq!(want, up.contents().unwrap());
232 up.free_contents(); 223 up.zero_contents();
233 assert_eq!("", up.contents().unwrap()); 224 assert_eq!("", up.contents().unwrap());
234 } 225 }
235 226
236 fn round_trip(msgs: Vec<OwnedMessage>) -> Answers { 227 fn round_trip(msgs: Vec<OwnedMessage>) -> Answers {
237 let n = msgs.len(); 228 let n = msgs.len();
291 } 282 }
292 } 283 }
293 284
294 #[test] 285 #[test]
295 fn test_text_answer() { 286 fn test_text_answer() {
296 let answer_ptr: *mut Answer = memory::calloc(1); 287 let mut answer: CHeapBox<Answer> = CHeapBox::default();
297 let answer = unsafe { &mut *answer_ptr }; 288 TextAnswer::fill(&mut answer, "hello").unwrap();
298 TextAnswer::fill(answer, "hello").unwrap(); 289 let zeroth_text = unsafe { TextAnswer::upcast(&mut answer) };
299 let zeroth_text = unsafe { TextAnswer::upcast(answer) };
300 let data = zeroth_text.contents().expect("valid"); 290 let data = zeroth_text.contents().expect("valid");
301 assert_eq!("hello", data); 291 assert_eq!("hello", data);
302 zeroth_text.free_contents(); 292 zeroth_text.zero_contents();
303 zeroth_text.free_contents(); 293 zeroth_text.zero_contents();
304 TextAnswer::fill(answer, "hell\0").expect_err("should error; contains nul"); 294 TextAnswer::fill(&mut answer, "hell\0").expect_err("should error; contains nul");
305 unsafe { memory::free(answer_ptr) }
306 } 295 }
307 296
308 #[test] 297 #[test]
309 fn test_binary_answer() { 298 fn test_binary_answer() {
310 use crate::conv::BinaryData; 299 use crate::conv::BinaryData;
311 let answer_ptr: *mut Answer = memory::calloc(1); 300 let mut answer: CHeapBox<Answer> = CHeapBox::default();
312 let answer = unsafe { &mut *answer_ptr };
313 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9); 301 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9);
314 BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed"); 302 BinaryAnswer::fill(&mut answer, (&real_data).into()).expect("alloc should succeed");
315 let bin_answer = unsafe { BinaryAnswer::upcast(answer) }; 303 let bin_answer = unsafe { BinaryAnswer::upcast(&mut answer) };
316 assert_eq!(real_data, unsafe { 304 assert_eq!(real_data, unsafe {
317 CBinaryData::as_binary_data(bin_answer.data().unwrap()) 305 CBinaryData::as_binary_data(bin_answer.data().unwrap())
318 }); 306 });
319 answer.free_contents();
320 answer.free_contents();
321 unsafe { memory::free(answer_ptr) }
322 } 307 }
323 308
324 #[test] 309 #[test]
325 #[ignore] 310 #[ignore]
326 fn test_binary_answer_too_big() { 311 fn test_binary_answer_too_big() {
327 let big_data: Vec<u8> = vec![0xFFu8; 0x1_0000_0001]; 312 let big_data: Vec<u8> = vec![0xFFu8; 0x1_0000_0001];
328 let answer_ptr: *mut Answer = memory::calloc(1); 313 let mut answer: CHeapBox<Answer> = CHeapBox::default();
329 let answer = unsafe { &mut *answer_ptr }; 314 BinaryAnswer::fill(&mut answer, (&big_data, 100)).expect_err("this is too big!");
330 BinaryAnswer::fill(answer, (&big_data, 100)).expect_err("this is too big!"); 315 }
331 answer.free_contents(); 316 }
332 unsafe { memory::free(answer) }
333 }
334 }