comparison src/libpam/answer.rs @ 93:efc2b56c8928

Remove undefined behavior per MIRI. This replaces a bunch of raw pointers with NonNull and removes all the undefined behavior that we can find with MIRI. We also remove the `SecureString` dependency (since it doesn't work with MIRI, and because it's not really necessary).
author Paul Fisher <paul@pfish.zone>
date Mon, 23 Jun 2025 13:02:58 -0400
parents 05291b601f0a
children b87100c5eed4
comparison
equal deleted inserted replaced
92:5ddbcada30f2 93:efc2b56c8928
5 use crate::libpam::memory::CBinaryData; 5 use crate::libpam::memory::CBinaryData;
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::{iter, mem, ptr, slice}; 11 use std::{iter, mem, ptr, slice};
11 12
12 /// The corridor via which the answer to Messages navigate through PAM. 13 /// The corridor via which the answer to Messages navigate through PAM.
13 #[derive(Debug)] 14 #[derive(Debug)]
14 pub struct Answers { 15 pub struct Answers {
25 }; 26 };
26 // Even if we fail during this process, we still end up freeing 27 // Even if we fail during this process, we still end up freeing
27 // all allocated answer memory. 28 // all allocated answer memory.
28 for (input, output) in iter::zip(value, outputs.iter_mut()) { 29 for (input, output) in iter::zip(value, outputs.iter_mut()) {
29 match input { 30 match input {
30 OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.unsecure())?, 31 OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.as_ref())?,
31 OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, 32 OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?,
32 OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, 33 OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?,
33 OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, 34 OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?,
34 // If we're here, that means that we *got* a Linux-PAM 35 // If we're here, that means that we *got* a Linux-PAM
35 // question from PAM, so we're OK to answer it. 36 // question from PAM, so we're OK to answer it.
105 106
106 /// Converts the `Answer` to a `TextAnswer` with the given text. 107 /// Converts the `Answer` to a `TextAnswer` with the given text.
107 fn fill(dest: &mut Answer, text: &str) -> Result<()> { 108 fn fill(dest: &mut Answer, text: &str) -> Result<()> {
108 let allocated = memory::malloc_str(text)?; 109 let allocated = memory::malloc_str(text)?;
109 dest.free_contents(); 110 dest.free_contents();
110 dest.data = allocated.cast(); 111 dest.data = allocated.as_ptr().cast();
111 Ok(()) 112 Ok(())
112 } 113 }
113 114
114 /// Gets the string stored in this answer. 115 /// Gets the string stored in this answer.
115 pub fn contents(&self) -> Result<&str> { 116 pub fn contents(&self) -> Result<&str> {
166 /// The referenced data is copied to the C heap. 167 /// The referenced data is copied to the C heap.
167 /// We do not take ownership of the original data. 168 /// We do not take ownership of the original data.
168 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { 169 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> {
169 let allocated = CBinaryData::alloc(data_and_type)?; 170 let allocated = CBinaryData::alloc(data_and_type)?;
170 dest.free_contents(); 171 dest.free_contents();
171 dest.data = allocated.cast(); 172 dest.data = allocated.as_ptr().cast();
172 Ok(()) 173 Ok(())
173 } 174 }
174 175
175 /// Gets the binary data in this answer. 176 /// Gets the binary data in this answer.
176 pub fn data(&self) -> Option<&CBinaryData> { 177 pub fn data(&self) -> Option<NonNull<CBinaryData>> {
177 // SAFETY: We either got this data from PAM or allocated it ourselves. 178 // SAFETY: We either got this data from PAM or allocated it ourselves.
178 // Either way, we trust that it is either valid data or null. 179 // Either way, we trust that it is either valid data or null.
179 unsafe { self.0.data.cast::<CBinaryData>().as_ref() } 180 NonNull::new(self.0.data.cast::<CBinaryData>())
180 } 181 }
181 182
182 /// Zeroes out the answer data, frees it, and points our data to `null`. 183 /// Zeroes out the answer data, frees it, and points our data to `null`.
183 /// 184 ///
184 /// When this `TextAnswer` is part of an [`Answers`], 185 /// When this `BinaryAnswer` is part of an [`Answers`],
185 /// this is optional (since that will perform the `free`), 186 /// this is optional (since that will perform the `free`),
186 /// but it will clear potentially sensitive data. 187 /// but it will clear potentially sensitive data.
187 pub fn zero_contents(&mut self) { 188 pub fn zero_contents(&mut self) {
188 // SAFETY: We know that our data pointer is either valid or null. 189 // SAFETY: We know that our data pointer is either valid or null.
189 // Once we're done, it's null and the answer is safe. 190 // Once we're done, it's null and the answer is safe.
190 unsafe { 191 unsafe {
191 let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); 192 if let Some(ptr) = NonNull::new(self.0.data) {
192 if let Some(d) = data_ref { 193 CBinaryData::zero_contents(ptr.cast())
193 d.zero_contents()
194 } 194 }
195 memory::free(self.0.data); 195 memory::free(self.0.data);
196 self.0.data = ptr::null_mut() 196 self.0.data = ptr::null_mut()
197 } 197 }
198 } 198 }
275 ), 275 ),
276 ]); 276 ]);
277 277
278 if let [bin, radio] = &mut answers[..] { 278 if let [bin, radio] = &mut answers[..] {
279 let up = unsafe { BinaryAnswer::upcast(bin) }; 279 let up = unsafe { BinaryAnswer::upcast(bin) };
280 assert_eq!(BinaryData::from((&[1, 2, 3][..], 99)), up.data().into()); 280 assert_eq!(BinaryData::from((&[1, 2, 3][..], 99)), unsafe {
281 CBinaryData::as_binary_data(up.data().unwrap())
282 });
281 up.zero_contents(); 283 up.zero_contents();
282 assert_eq!(BinaryData::default(), up.data().into()); 284 assert_eq!(BinaryData::default(), unsafe {
285 CBinaryData::as_binary_data(up.data().unwrap())
286 });
283 287
284 assert_text_answer("beep boop", radio); 288 assert_text_answer("beep boop", radio);
285 } else { 289 } else {
286 panic!("received wrong size {len}!", len = answers.len()) 290 panic!("received wrong size {len}!", len = answers.len())
287 } 291 }
307 let answer_ptr: *mut Answer = memory::calloc(1); 311 let answer_ptr: *mut Answer = memory::calloc(1);
308 let answer = unsafe { &mut *answer_ptr }; 312 let answer = unsafe { &mut *answer_ptr };
309 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9); 313 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9);
310 BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed"); 314 BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed");
311 let bin_answer = unsafe { BinaryAnswer::upcast(answer) }; 315 let bin_answer = unsafe { BinaryAnswer::upcast(answer) };
312 assert_eq!(real_data, bin_answer.data().into()); 316 assert_eq!(real_data, unsafe {
317 CBinaryData::as_binary_data(bin_answer.data().unwrap())
318 });
313 answer.free_contents(); 319 answer.free_contents();
314 answer.free_contents(); 320 answer.free_contents();
315 unsafe { memory::free(answer_ptr) } 321 unsafe { memory::free(answer_ptr) }
316 } 322 }
317 323