Mercurial > crates > nonstick
comparison src/libpam/answer.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 | 05291b601f0a |
children | b87100c5eed4 |
comparison
equal
deleted
inserted
replaced
92:5ddbcada30f2 | 93:efc2b56c8928 |
---|---|
5 use crate::libpam::memory::CBinaryData; | 5 use crate::libpam::memory::CBinaryData; |
6 pub use crate::libpam::pam_ffi::Answer; | 6 pub use crate::libpam::pam_ffi::Answer; |
7 use crate::{ErrorCode, Result}; | 7 use crate::{ErrorCode, Result}; |
8 use std::ffi::CStr; | 8 use std::ffi::CStr; |
9 use std::ops::{Deref, DerefMut}; | 9 use std::ops::{Deref, DerefMut}; |
10 use std::ptr::NonNull; | |
10 use std::{iter, mem, ptr, slice}; | 11 use std::{iter, mem, ptr, slice}; |
11 | 12 |
12 /// The corridor via which the answer to Messages navigate through PAM. | 13 /// The corridor via which the answer to Messages navigate through PAM. |
13 #[derive(Debug)] | 14 #[derive(Debug)] |
14 pub struct Answers { | 15 pub struct Answers { |
25 }; | 26 }; |
26 // Even if we fail during this process, we still end up freeing | 27 // Even if we fail during this process, we still end up freeing |
27 // all allocated answer memory. | 28 // all allocated answer memory. |
28 for (input, output) in iter::zip(value, outputs.iter_mut()) { | 29 for (input, output) in iter::zip(value, outputs.iter_mut()) { |
29 match input { | 30 match input { |
30 OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.unsecure())?, | 31 OwnedMessage::MaskedPrompt(p) => TextAnswer::fill(output, p.answer()?.as_ref())?, |
31 OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, | 32 OwnedMessage::Prompt(p) => TextAnswer::fill(output, &(p.answer()?))?, |
32 OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | 33 OwnedMessage::Error(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, |
33 OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, | 34 OwnedMessage::Info(p) => TextAnswer::fill(output, p.answer().map(|_| "")?)?, |
34 // If we're here, that means that we *got* a Linux-PAM | 35 // If we're here, that means that we *got* a Linux-PAM |
35 // question from PAM, so we're OK to answer it. | 36 // question from PAM, so we're OK to answer it. |
105 | 106 |
106 /// Converts the `Answer` to a `TextAnswer` with the given text. | 107 /// Converts the `Answer` to a `TextAnswer` with the given text. |
107 fn fill(dest: &mut Answer, text: &str) -> Result<()> { | 108 fn fill(dest: &mut Answer, text: &str) -> Result<()> { |
108 let allocated = memory::malloc_str(text)?; | 109 let allocated = memory::malloc_str(text)?; |
109 dest.free_contents(); | 110 dest.free_contents(); |
110 dest.data = allocated.cast(); | 111 dest.data = allocated.as_ptr().cast(); |
111 Ok(()) | 112 Ok(()) |
112 } | 113 } |
113 | 114 |
114 /// Gets the string stored in this answer. | 115 /// Gets the string stored in this answer. |
115 pub fn contents(&self) -> Result<&str> { | 116 pub fn contents(&self) -> Result<&str> { |
166 /// The referenced data is copied to the C heap. | 167 /// The referenced data is copied to the C heap. |
167 /// We do not take ownership of the original data. | 168 /// We do not take ownership of the original data. |
168 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { | 169 pub fn fill(dest: &mut Answer, data_and_type: (&[u8], u8)) -> Result<()> { |
169 let allocated = CBinaryData::alloc(data_and_type)?; | 170 let allocated = CBinaryData::alloc(data_and_type)?; |
170 dest.free_contents(); | 171 dest.free_contents(); |
171 dest.data = allocated.cast(); | 172 dest.data = allocated.as_ptr().cast(); |
172 Ok(()) | 173 Ok(()) |
173 } | 174 } |
174 | 175 |
175 /// Gets the binary data in this answer. | 176 /// Gets the binary data in this answer. |
176 pub fn data(&self) -> Option<&CBinaryData> { | 177 pub fn data(&self) -> Option<NonNull<CBinaryData>> { |
177 // SAFETY: We either got this data from PAM or allocated it ourselves. | 178 // SAFETY: We either got this data from PAM or allocated it ourselves. |
178 // Either way, we trust that it is either valid data or null. | 179 // Either way, we trust that it is either valid data or null. |
179 unsafe { self.0.data.cast::<CBinaryData>().as_ref() } | 180 NonNull::new(self.0.data.cast::<CBinaryData>()) |
180 } | 181 } |
181 | 182 |
182 /// Zeroes out the answer data, frees it, and points our data to `null`. | 183 /// Zeroes out the answer data, frees it, and points our data to `null`. |
183 /// | 184 /// |
184 /// When this `TextAnswer` is part of an [`Answers`], | 185 /// When this `BinaryAnswer` is part of an [`Answers`], |
185 /// this is optional (since that will perform the `free`), | 186 /// this is optional (since that will perform the `free`), |
186 /// but it will clear potentially sensitive data. | 187 /// but it will clear potentially sensitive data. |
187 pub fn zero_contents(&mut self) { | 188 pub fn zero_contents(&mut self) { |
188 // SAFETY: We know that our data pointer is either valid or null. | 189 // SAFETY: We know that our data pointer is either valid or null. |
189 // Once we're done, it's null and the answer is safe. | 190 // Once we're done, it's null and the answer is safe. |
190 unsafe { | 191 unsafe { |
191 let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); | 192 if let Some(ptr) = NonNull::new(self.0.data) { |
192 if let Some(d) = data_ref { | 193 CBinaryData::zero_contents(ptr.cast()) |
193 d.zero_contents() | |
194 } | 194 } |
195 memory::free(self.0.data); | 195 memory::free(self.0.data); |
196 self.0.data = ptr::null_mut() | 196 self.0.data = ptr::null_mut() |
197 } | 197 } |
198 } | 198 } |
275 ), | 275 ), |
276 ]); | 276 ]); |
277 | 277 |
278 if let [bin, radio] = &mut answers[..] { | 278 if let [bin, radio] = &mut answers[..] { |
279 let up = unsafe { BinaryAnswer::upcast(bin) }; | 279 let up = unsafe { BinaryAnswer::upcast(bin) }; |
280 assert_eq!(BinaryData::from((&[1, 2, 3][..], 99)), up.data().into()); | 280 assert_eq!(BinaryData::from((&[1, 2, 3][..], 99)), unsafe { |
281 CBinaryData::as_binary_data(up.data().unwrap()) | |
282 }); | |
281 up.zero_contents(); | 283 up.zero_contents(); |
282 assert_eq!(BinaryData::default(), up.data().into()); | 284 assert_eq!(BinaryData::default(), unsafe { |
285 CBinaryData::as_binary_data(up.data().unwrap()) | |
286 }); | |
283 | 287 |
284 assert_text_answer("beep boop", radio); | 288 assert_text_answer("beep boop", radio); |
285 } else { | 289 } else { |
286 panic!("received wrong size {len}!", len = answers.len()) | 290 panic!("received wrong size {len}!", len = answers.len()) |
287 } | 291 } |
307 let answer_ptr: *mut Answer = memory::calloc(1); | 311 let answer_ptr: *mut Answer = memory::calloc(1); |
308 let answer = unsafe { &mut *answer_ptr }; | 312 let answer = unsafe { &mut *answer_ptr }; |
309 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9); | 313 let real_data = BinaryData::new([1, 2, 3, 4, 5, 6, 7, 8], 9); |
310 BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed"); | 314 BinaryAnswer::fill(answer, (&real_data).into()).expect("alloc should succeed"); |
311 let bin_answer = unsafe { BinaryAnswer::upcast(answer) }; | 315 let bin_answer = unsafe { BinaryAnswer::upcast(answer) }; |
312 assert_eq!(real_data, bin_answer.data().into()); | 316 assert_eq!(real_data, unsafe { |
317 CBinaryData::as_binary_data(bin_answer.data().unwrap()) | |
318 }); | |
313 answer.free_contents(); | 319 answer.free_contents(); |
314 answer.free_contents(); | 320 answer.free_contents(); |
315 unsafe { memory::free(answer_ptr) } | 321 unsafe { memory::free(answer_ptr) } |
316 } | 322 } |
317 | 323 |