comparison src/libpam/question.rs @ 101:94b51fa4f797

Fix memory soundness issues: - Ensure Questions are pinned in memory when sending them through PAM. - Hold on to the PAM conversation struct after we build it. (Linux-PAM is leninent about this and copies the pam_conv structure.)
author Paul Fisher <paul@pfish.zone>
date Tue, 24 Jun 2025 17:54:33 -0400
parents b87100c5eed4
children
comparison
equal deleted inserted replaced
100:3f11b8d30f63 101:94b51fa4f797
9 pub use crate::libpam::pam_ffi::Question; 9 pub use crate::libpam::pam_ffi::Question;
10 use crate::ErrorCode; 10 use crate::ErrorCode;
11 use crate::Result; 11 use crate::Result;
12 use num_enum::{IntoPrimitive, TryFromPrimitive}; 12 use num_enum::{IntoPrimitive, TryFromPrimitive};
13 use std::ffi::{c_void, CStr}; 13 use std::ffi::{c_void, CStr};
14 use std::pin::Pin;
14 use std::slice; 15 use std::slice;
15 16
16 /// Abstraction of a collection of questions to be sent in a PAM conversation. 17 /// Abstraction of a collection of questions to be sent in a PAM conversation.
17 /// 18 ///
18 /// The PAM C API conversation function looks like this: 19 /// The PAM C API conversation function looks like this:
62 fn new(messages: &[Message]) -> Result<Self> 63 fn new(messages: &[Message]) -> Result<Self>
63 where 64 where
64 Self: Sized; 65 Self: Sized;
65 66
66 /// Gets the pointer that is passed . 67 /// Gets the pointer that is passed .
67 fn ptr(&self) -> *const *const Question; 68 fn ptr(self: Pin<&Self>) -> *const *const Question;
68 69
69 /// Converts a pointer into a borrowed list of Questions. 70 /// Converts a pointer into a borrowed list of Questions.
70 /// 71 ///
71 /// # Safety 72 /// # Safety
72 /// 73 ///
113 questions, 114 questions,
114 _marker: Default::default(), 115 _marker: Default::default(),
115 }) 116 })
116 } 117 }
117 118
118 fn ptr(&self) -> *const *const Question { 119 fn ptr(self: Pin<&Self>) -> *const *const Question {
119 &self.pointer as *const *const Question 120 &self.pointer as *const *const Question
120 } 121 }
121 122
122 unsafe fn borrow_ptr<'a>( 123 unsafe fn borrow_ptr<'a>(
123 ptr: *const *const Question, 124 ptr: *const *const Question,
129 130
130 /// The Linux version of the pointer train to questions. 131 /// The Linux version of the pointer train to questions.
131 #[derive(Debug)] 132 #[derive(Debug)]
132 #[repr(C)] 133 #[repr(C)]
133 pub struct LinuxPamQuestions { 134 pub struct LinuxPamQuestions {
134 #[allow(clippy::vec_box)] // we need to do this. 135 #[allow(clippy::vec_box)] // we need to box vec items.
135 /// The place where the questions are. 136 /// The place where the questions are.
136 questions: Vec<Box<Question>>, 137 questions: Vec<Box<Question>>,
137 _marker: Immovable,
138 } 138 }
139 139
140 impl LinuxPamQuestions { 140 impl LinuxPamQuestions {
141 fn len(&self) -> usize { 141 fn len(&self) -> usize {
142 self.questions.len() 142 self.questions.len()
153 .iter() 153 .iter()
154 .map(|msg| Question::try_from(msg).map(Box::new)) 154 .map(|msg| Question::try_from(msg).map(Box::new))
155 .collect(); 155 .collect();
156 Ok(Self { 156 Ok(Self {
157 questions: questions?, 157 questions: questions?,
158 _marker: Default::default(),
159 }) 158 })
160 } 159 }
161 160
162 fn ptr(&self) -> *const *const Question { 161 fn ptr(self: Pin<&Self>) -> *const *const Question {
163 self.questions.as_ptr().cast() 162 self.questions.as_ptr().cast()
164 } 163 }
165 164
166 unsafe fn borrow_ptr<'a>( 165 unsafe fn borrow_ptr<'a>(
167 ptr: *const *const Question, 166 ptr: *const *const Question,
244 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), 243 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError),
245 }?; 244 }?;
246 Ok(Self { 245 Ok(Self {
247 style: style.into(), 246 style: style.into(),
248 data: Some(data), 247 data: Some(data),
249 _marker: Default::default(),
250 }) 248 })
251 } 249 }
252 } 250 }
253 251
254 impl Drop for Question { 252 impl Drop for Question {
326 macro_rules! tests { ($fn_name:ident<$typ:ident>) => { 324 macro_rules! tests { ($fn_name:ident<$typ:ident>) => {
327 mod $fn_name { 325 mod $fn_name {
328 use super::super::*; 326 use super::super::*;
329 #[test] 327 #[test]
330 fn standard() { 328 fn standard() {
331 let interrogation = <$typ>::new(&[ 329 let interrogation = Box::pin(<$typ>::new(&[
332 MaskedQAndA::new("hocus pocus").message(), 330 MaskedQAndA::new("hocus pocus").message(),
333 QAndA::new("what").message(), 331 QAndA::new("what").message(),
334 QAndA::new("who").message(), 332 QAndA::new("who").message(),
335 InfoMsg::new("hey").message(), 333 InfoMsg::new("hey").message(),
336 ErrorMsg::new("gasp").message(), 334 ErrorMsg::new("gasp").message(),
337 ]) 335 ])
338 .unwrap(); 336 .unwrap());
339 let indirect = interrogation.ptr(); 337 let indirect = interrogation.as_ref().ptr();
340 338
341 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) }; 339 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) };
342 let messages: Vec<OwnedMessage> = remade 340 let messages: Vec<OwnedMessage> = remade
343 .map(TryInto::try_into) 341 .map(TryInto::try_into)
344 .collect::<Result<_>>() 342 .collect::<Result<_>>()
362 } 360 }
363 361
364 #[test] 362 #[test]
365 #[cfg(feature = "linux-pam-extensions")] 363 #[cfg(feature = "linux-pam-extensions")]
366 fn linux_extensions() { 364 fn linux_extensions() {
367 let interrogation = <$typ>::new(&[ 365 let interrogation = Box::pin(<$typ>::new(&[
368 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(), 366 BinaryQAndA::new((&[5, 4, 3, 2, 1], 66)).message(),
369 RadioQAndA::new("you must choose").message(), 367 RadioQAndA::new("you must choose").message(),
370 ]).unwrap(); 368 ]).unwrap());
371 let indirect = interrogation.ptr(); 369 let indirect = interrogation.as_ref().ptr();
372 370
373 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) }; 371 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) };
374 let messages: Vec<OwnedMessage> = remade 372 let messages: Vec<OwnedMessage> = remade
375 .map(TryInto::try_into) 373 .map(TryInto::try_into)
376 .collect::<Result<_>>() 374 .collect::<Result<_>>()