Mercurial > crates > nonstick
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 | 13b4d2a19674 |
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<_>>() |
