comparison src/libpam/answer.rs @ 139:33b9622ed6d2

Remove redundant memory management in nonstick::libpam; fix UB. - Uses the libpam-sys-helpers BinaryPayload / OwnedBinaryPayload structs to handle memory management and parsing for Linux-PAM binary messages. - Gets rid of the (technically) undefined behavior in PtrPtrVec due to pointer provenance. - Don't check for malloc failing. It won't, even if it does. - Formatting/cleanups/etc.
author Paul Fisher <paul@pfish.zone>
date Thu, 03 Jul 2025 23:57:49 -0400
parents 80c07e5ab22f
children ebb71a412b58
comparison
equal deleted inserted replaced
138:999bf07efbcb 139:33b9622ed6d2
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::OwnedExchange; 3 use crate::libpam::conversation::OwnedExchange;
4 use crate::libpam::memory; 4 use crate::libpam::memory;
5 use crate::libpam::memory::{CBinaryData, CHeapBox, CHeapString, Immovable}; 5 use crate::libpam::memory::{CHeapBox, CHeapPayload, CHeapString, Immovable};
6 use crate::{ErrorCode, Result}; 6 use crate::{ErrorCode, Result};
7 use libpam_sys_helpers::memory::BinaryPayload;
7 use std::ffi::{c_int, c_void, CStr}; 8 use std::ffi::{c_int, c_void, CStr};
8 use std::mem::ManuallyDrop; 9 use std::mem::ManuallyDrop;
9 use std::ops::{Deref, DerefMut}; 10 use std::ops::{Deref, DerefMut};
10 use std::ptr::NonNull; 11 use std::ptr::NonNull;
11 use std::{iter, ptr, slice}; 12 use std::{iter, ptr, slice};
21 22
22 impl Answers { 23 impl Answers {
23 /// Builds an Answers out of the given answered Message Q&As. 24 /// Builds an Answers out of the given answered Message Q&As.
24 pub fn build(value: Vec<OwnedExchange>) -> Result<Self> { 25 pub fn build(value: Vec<OwnedExchange>) -> Result<Self> {
25 let mut outputs = Self { 26 let mut outputs = Self {
26 base: memory::calloc(value.len())?, 27 base: memory::calloc(value.len()),
27 count: value.len(), 28 count: value.len(),
28 }; 29 };
29 // Even if we fail during this process, we still end up freeing 30 // Even if we fail during this process, we still end up freeing
30 // all allocated answer memory. 31 // all allocated answer memory.
31 for (input, output) in iter::zip(value, outputs.iter_mut()) { 32 for (input, output) in iter::zip(value, outputs.iter_mut()) {
191 /// The `data_type` is a tag you can use for whatever. 192 /// The `data_type` is a tag you can use for whatever.
192 /// It is passed through PAM unchanged. 193 /// It is passed through PAM unchanged.
193 /// 194 ///
194 /// The referenced data is copied to the C heap. 195 /// The referenced data is copied to the C heap.
195 /// We do not take ownership of the original data. 196 /// We do not take ownership of the original data.
196 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { 197 pub fn fill(dest: &mut Answer, (data, type_): (&[u8], u8)) -> Result<()> {
197 let allocated = CBinaryData::alloc(data_and_type)?; 198 let payload = CHeapPayload::new(data, type_).map_err(|_| ErrorCode::BufferError)?;
198 let _ = dest.data.replace(unsafe { CHeapBox::cast(allocated) }); 199 let _ = dest
200 .data
201 .replace(unsafe { CHeapBox::cast(payload.into_inner()) });
199 Ok(()) 202 Ok(())
200 } 203 }
201 204
202 /// Gets the binary data in this answer. 205 /// Gets the binary data in this answer.
203 pub fn data(&self) -> Option<NonNull<CBinaryData>> { 206 pub fn contents(&self) -> Option<(&[u8], u8)> {
204 // SAFETY: We either got this data from PAM or allocated it ourselves. 207 // SAFETY: We either got this data from PAM or allocated it ourselves.
205 // Either way, we trust that it is either valid data or null. 208 // Either way, we trust that it is either valid data or null.
206 self.0 209 self.0
207 .data 210 .data
208 .as_ref() 211 .as_ref()
209 .map(CHeapBox::as_ptr) 212 .map(|data| unsafe { BinaryPayload::contents(CHeapBox::as_ptr(data).cast().as_ptr()) })
210 .map(NonNull::cast)
211 } 213 }
212 214
213 /// Zeroes out the answer data, frees it, and points our data to `null`. 215 /// Zeroes out the answer data, frees it, and points our data to `null`.
214 /// 216 ///
215 /// When this `BinaryAnswer` is part of an [`Answers`], 217 /// When this `BinaryAnswer` is part of an [`Answers`],
216 /// this is optional (since that will perform the `free`), 218 /// this is optional (since that will perform the `free`),
217 /// but it will clear potentially sensitive data. 219 /// but it will clear potentially sensitive data.
218 pub fn zero_contents(&mut self) { 220 pub fn zero_contents(&mut self) {
219 // SAFETY: We know that our data pointer is either valid or null. 221 // SAFETY: We know that our data pointer is either valid or null.
220 // Once we're done, it's null and the answer is safe. 222 if let Some(data) = self.0.data.as_mut() {
221 unsafe { 223 unsafe {
222 if let Some(ptr) = self.0.data.as_ref() { 224 let total = BinaryPayload::total_bytes(CHeapBox::as_ptr(data).cast().as_ref());
223 CBinaryData::zero_contents(CHeapBox::as_ptr(ptr).cast()) 225 let data: &mut [u8] =
226 slice::from_raw_parts_mut(CHeapBox::as_raw_ptr(data).cast(), total);
227 data.fill(0)
224 } 228 }
225 } 229 }
226 } 230 }
227 } 231 }
228 232
288 ), 292 ),
289 ]); 293 ]);
290 294
291 if let [bin, radio] = &mut answers[..] { 295 if let [bin, radio] = &mut answers[..] {
292 let up = unsafe { BinaryAnswer::upcast(bin) }; 296 let up = unsafe { BinaryAnswer::upcast(bin) };
293 assert_eq!(BinaryData::from((&[1, 2, 3][..], 99)), unsafe { 297 assert_eq!((&[1, 2, 3][..], 99), up.contents().unwrap());
294 CBinaryData::as_binary_data(up.data().unwrap())
295 });
296 up.zero_contents(); 298 up.zero_contents();
297 assert_eq!(BinaryData::default(), unsafe { 299 assert_eq!((&[][..], 0), up.contents().unwrap());
298 CBinaryData::as_binary_data(up.data().unwrap())
299 });
300 300
301 assert_text_answer("beep boop", radio); 301 assert_text_answer("beep boop", radio);
302 } else { 302 } else {
303 panic!("received wrong size {len}!", len = answers.len()) 303 panic!("received wrong size {len}!", len = answers.len())
304 } 304 }
321 use crate::conv::BinaryData; 321 use crate::conv::BinaryData;
322 let mut answer: CHeapBox<Answer> = CHeapBox::default(); 322 let mut answer: CHeapBox<Answer> = CHeapBox::default();
323 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9); 323 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9);
324 BinaryAnswer::fill(&mut answer, (&real_data).into()).expect("alloc should succeed"); 324 BinaryAnswer::fill(&mut answer, (&real_data).into()).expect("alloc should succeed");
325 let bin_answer = unsafe { BinaryAnswer::upcast(&mut answer) }; 325 let bin_answer = unsafe { BinaryAnswer::upcast(&mut answer) };
326 assert_eq!(real_data, unsafe { 326 assert_eq!(real_data, bin_answer.contents().unwrap().into());
327 CBinaryData::as_binary_data(bin_answer.data().unwrap())
328 });
329 } 327 }
330 328
331 #[test] 329 #[test]
332 #[ignore] 330 #[ignore]
333 fn test_binary_answer_too_big() { 331 fn test_binary_answer_too_big() {