comparison src/libpam/question.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 dd3e9c4bcde3
children b87100c5eed4
comparison
equal deleted inserted replaced
92:5ddbcada30f2 93:efc2b56c8928
9 use crate::libpam::{memory, pam_ffi}; 9 use crate::libpam::{memory, pam_ffi};
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::ptr::NonNull;
14 use std::{ptr, slice}; 15 use std::{ptr, 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:
212 } 213 }
213 } 214 }
214 215
215 /// Gets this message's data pointer as borrowed binary data. 216 /// Gets this message's data pointer as borrowed binary data.
216 unsafe fn binary_data(&self) -> (&[u8], u8) { 217 unsafe fn binary_data(&self) -> (&[u8], u8) {
217 self.data 218 NonNull::new(self.data)
218 .cast::<CBinaryData>() 219 .map(|nn| nn.cast())
219 .as_ref() 220 .map(|ptr| CBinaryData::data(ptr))
220 .map(Into::into)
221 .unwrap_or_default() 221 .unwrap_or_default()
222 } 222 }
223 } 223 }
224 224
225 impl TryFrom<&Message<'_>> for Question { 225 impl TryFrom<&Message<'_>> for Question {
226 type Error = ErrorCode; 226 type Error = ErrorCode;
227 fn try_from(msg: &Message) -> Result<Self> { 227 fn try_from(msg: &Message) -> Result<Self> {
228 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast())); 228 let alloc = |style, text| Ok((style, memory::malloc_str(text)?.cast()));
229 // We will only allocate heap data if we have a valid input. 229 // We will only allocate heap data if we have a valid input.
230 let (style, data): (_, *mut c_void) = match *msg { 230 let (style, data): (_, NonNull<c_void>) = match *msg {
231 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()), 231 Message::MaskedPrompt(p) => alloc(Style::PromptEchoOff, p.question()),
232 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()), 232 Message::Prompt(p) => alloc(Style::PromptEchoOn, p.question()),
233 Message::Error(p) => alloc(Style::ErrorMsg, p.question()), 233 Message::Error(p) => alloc(Style::ErrorMsg, p.question()),
234 Message::Info(p) => alloc(Style::TextInfo, p.question()), 234 Message::Info(p) => alloc(Style::TextInfo, p.question()),
235 #[cfg(feature = "linux-pam-extensions")] 235 #[cfg(feature = "linux-pam-extensions")]
242 #[cfg(not(feature = "linux-pam-extensions"))] 242 #[cfg(not(feature = "linux-pam-extensions"))]
243 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError), 243 Message::RadioPrompt(_) | Message::BinaryPrompt(_) => Err(ErrorCode::ConversationError),
244 }?; 244 }?;
245 Ok(Self { 245 Ok(Self {
246 style: style.into(), 246 style: style.into(),
247 data, 247 data: data.as_ptr(),
248 _marker: Default::default(), 248 _marker: Default::default(),
249 }) 249 })
250 } 250 }
251 } 251 }
252 252
259 // in the Question. If it's not a supported format, we skip it. 259 // in the Question. If it's not a supported format, we skip it.
260 if let Ok(style) = Style::try_from(self.style) { 260 if let Ok(style) = Style::try_from(self.style) {
261 match style { 261 match style {
262 #[cfg(feature = "linux-pam-extensions")] 262 #[cfg(feature = "linux-pam-extensions")]
263 Style::BinaryPrompt => { 263 Style::BinaryPrompt => {
264 if let Some(d) = self.data.cast::<CBinaryData>().as_mut() { 264 if let Some(d) = NonNull::new(self.data) {
265 d.zero_contents() 265 CBinaryData::zero_contents(d.cast())
266 } 266 }
267 } 267 }
268 #[cfg(feature = "linux-pam-extensions")] 268 #[cfg(feature = "linux-pam-extensions")]
269 Style::RadioType => memory::zero_c_string(self.data.cast()), 269 Style::RadioType => memory::zero_c_string(self.data.cast()),
270 Style::TextInfo 270 Style::TextInfo
365 RadioQAndA::new("you must choose").message(), 365 RadioQAndA::new("you must choose").message(),
366 ]).unwrap(); 366 ]).unwrap();
367 let indirect = interrogation.ptr(); 367 let indirect = interrogation.ptr();
368 368
369 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) }; 369 let remade = unsafe { $typ::borrow_ptr(indirect, interrogation.len()) };
370 let messages: Vec<OwnedMessage> = unsafe { remade } 370 let messages: Vec<OwnedMessage> = remade
371 .map(TryInto::try_into) 371 .map(TryInto::try_into)
372 .collect::<Result<_>>() 372 .collect::<Result<_>>()
373 .unwrap(); 373 .unwrap();
374 let [bin, choose] = messages.try_into().unwrap(); 374 let [bin, choose] = messages.try_into().unwrap();
375 assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66)); 375 assert_matches!(bin => OwnedMessage::BinaryPrompt, (&[5, 4, 3, 2, 1][..], 66));