Mercurial > crates > nonstick
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)); |
