Mercurial > crates > nonstick
comparison src/libpam/memory.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 | 5aa1a010f1e8 |
| children | 51c9d7e8261a |
comparison
equal
deleted
inserted
replaced
| 92:5ddbcada30f2 | 93:efc2b56c8928 |
|---|---|
| 2 | 2 |
| 3 use crate::Result; | 3 use crate::Result; |
| 4 use crate::{BinaryData, ErrorCode}; | 4 use crate::{BinaryData, ErrorCode}; |
| 5 use std::ffi::{c_char, CStr, CString}; | 5 use std::ffi::{c_char, CStr, CString}; |
| 6 use std::marker::{PhantomData, PhantomPinned}; | 6 use std::marker::{PhantomData, PhantomPinned}; |
| 7 use std::{ptr, slice}; | 7 use std::mem::offset_of; |
| 8 use std::ptr::NonNull; | |
| 9 use std::{mem, ptr, slice}; | |
| 8 | 10 |
| 9 /// Allocates `count` elements to hold `T`. | 11 /// Allocates `count` elements to hold `T`. |
| 10 #[inline] | 12 #[inline] |
| 11 pub fn calloc<T>(count: usize) -> *mut T { | 13 pub fn calloc<T>(count: usize) -> *mut T { |
| 12 // SAFETY: it's always safe to allocate! Leaking memory is fun! | 14 // SAFETY: it's always safe to allocate! Leaking memory is fun! |
| 48 /// <code>pam_get_<var>whatever</var></code> function. | 50 /// <code>pam_get_<var>whatever</var></code> function. |
| 49 /// | 51 /// |
| 50 /// # Safety | 52 /// # Safety |
| 51 /// | 53 /// |
| 52 /// It's on you to provide a valid string. | 54 /// It's on you to provide a valid string. |
| 53 pub unsafe fn copy_pam_string(result_ptr: *const libc::c_char) -> Result<String> { | 55 pub unsafe fn copy_pam_string(result_ptr: *const c_char) -> Result<String> { |
| 54 // We really shouldn't get a null pointer back here, but if we do, return nothing. | 56 Ok(wrap_string(result_ptr)? |
| 55 if result_ptr.is_null() { | |
| 56 return Ok(String::new()); | |
| 57 } | |
| 58 let bytes = unsafe { CStr::from_ptr(result_ptr) }; | |
| 59 bytes | |
| 60 .to_str() | |
| 61 .map(String::from) | 57 .map(String::from) |
| 62 .map_err(|_| ErrorCode::ConversationError) | 58 .unwrap_or_default()) |
| 63 } | 59 } |
| 64 | 60 |
| 65 /// Wraps a string returned from PAM as an `Option<&str>`. | 61 /// Wraps a string returned from PAM as an `Option<&str>`. |
| 66 pub unsafe fn wrap_string<'a>(data: *const libc::c_char) -> Result<Option<&'a str>> { | 62 pub unsafe fn wrap_string<'a>(data: *const c_char) -> Result<Option<&'a str>> { |
| 67 let ret = if data.is_null() { | 63 match NonNull::new(data.cast_mut()) { |
| 68 None | 64 Some(data) => Ok(Some( |
| 69 } else { | 65 CStr::from_ptr(data.as_ptr()) |
| 70 Some( | |
| 71 CStr::from_ptr(data) | |
| 72 .to_str() | 66 .to_str() |
| 73 .map_err(|_| ErrorCode::ConversationError)?, | 67 .map_err(|_| ErrorCode::ConversationError)?, |
| 74 ) | 68 )), |
| 75 }; | 69 None => Ok(None), |
| 76 Ok(ret) | 70 } |
| 77 } | 71 } |
| 78 | 72 |
| 79 /// Allocates a string with the given contents on the C heap. | 73 /// Allocates a string with the given contents on the C heap. |
| 80 /// | 74 /// |
| 81 /// This is like [`CString::new`], but: | 75 /// This is like [`CString::new`], but: |
| 82 /// | 76 /// |
| 83 /// - it allocates data on the C heap with [`libc::malloc`]. | 77 /// - it allocates data on the C heap with [`libc::malloc`]. |
| 84 /// - it doesn't take ownership of the data passed in. | 78 /// - it doesn't take ownership of the data passed in. |
| 85 pub fn malloc_str(text: &str) -> Result<*mut c_char> { | 79 pub fn malloc_str(text: &str) -> Result<NonNull<c_char>> { |
| 86 let data = text.as_bytes(); | 80 let data = text.as_bytes(); |
| 87 if data.contains(&0) { | 81 if data.contains(&0) { |
| 88 return Err(ErrorCode::ConversationError); | 82 return Err(ErrorCode::ConversationError); |
| 89 } | 83 } |
| 90 // +1 for the null terminator | 84 // +1 for the null terminator |
| 91 let data_alloc: *mut c_char = calloc(data.len() + 1); | 85 let data_alloc: *mut c_char = calloc(data.len() + 1); |
| 92 // SAFETY: we just allocated this and we have enough room. | 86 // SAFETY: we just allocated this and we have enough room. |
| 93 unsafe { | 87 unsafe { |
| 94 libc::memcpy(data_alloc.cast(), data.as_ptr().cast(), data.len()); | 88 libc::memcpy(data_alloc.cast(), data.as_ptr().cast(), data.len()); |
| 95 } | 89 Ok(NonNull::new_unchecked(data_alloc)) |
| 96 Ok(data_alloc) | 90 } |
| 97 } | 91 } |
| 98 | 92 |
| 99 /// Writes zeroes over the contents of a C string. | 93 /// Writes zeroes over the contents of a C string. |
| 100 /// | 94 /// |
| 101 /// This won't overwrite a null pointer. | 95 /// This won't overwrite a null pointer. |
| 103 /// # Safety | 97 /// # Safety |
| 104 /// | 98 /// |
| 105 /// It's up to you to provide a valid C string. | 99 /// It's up to you to provide a valid C string. |
| 106 pub unsafe fn zero_c_string(cstr: *mut c_char) { | 100 pub unsafe fn zero_c_string(cstr: *mut c_char) { |
| 107 if !cstr.is_null() { | 101 if !cstr.is_null() { |
| 108 libc::memset(cstr.cast(), 0, libc::strlen(cstr.cast())); | 102 let len = libc::strlen(cstr.cast()); |
| 103 for x in 0..len { | |
| 104 ptr::write_volatile(cstr.byte_offset(x as isize), mem::zeroed()) | |
| 105 } | |
| 109 } | 106 } |
| 110 } | 107 } |
| 111 | 108 |
| 112 /// Binary data used in requests and responses. | 109 /// Binary data used in requests and responses. |
| 113 /// | 110 /// |
| 126 _marker: Immovable, | 123 _marker: Immovable, |
| 127 } | 124 } |
| 128 | 125 |
| 129 impl CBinaryData { | 126 impl CBinaryData { |
| 130 /// Copies the given data to a new BinaryData on the heap. | 127 /// Copies the given data to a new BinaryData on the heap. |
| 131 pub fn alloc((data, data_type): (&[u8], u8)) -> Result<*mut CBinaryData> { | 128 pub fn alloc((data, data_type): (&[u8], u8)) -> Result<NonNull<CBinaryData>> { |
| 132 let buffer_size = | 129 let buffer_size = |
| 133 u32::try_from(data.len() + 5).map_err(|_| ErrorCode::ConversationError)?; | 130 u32::try_from(data.len() + 5).map_err(|_| ErrorCode::ConversationError)?; |
| 134 // SAFETY: We're only allocating here. | 131 // SAFETY: We're only allocating here. |
| 135 let dest = unsafe { | 132 let dest = unsafe { |
| 136 let dest_buffer: *mut CBinaryData = calloc::<u8>(buffer_size as usize).cast(); | 133 let mut dest_buffer: NonNull<Self> = |
| 137 let dest = &mut *dest_buffer; | 134 NonNull::new_unchecked(calloc::<u8>(buffer_size as usize).cast()); |
| 135 let dest = dest_buffer.as_mut(); | |
| 138 dest.total_length = buffer_size.to_be_bytes(); | 136 dest.total_length = buffer_size.to_be_bytes(); |
| 139 dest.data_type = data_type; | 137 dest.data_type = data_type; |
| 140 let dest = dest.data.as_mut_ptr(); | 138 libc::memcpy( |
| 141 libc::memcpy(dest.cast(), data.as_ptr().cast(), data.len()); | 139 Self::data_ptr(dest_buffer).cast(), |
| 140 data.as_ptr().cast(), | |
| 141 data.len(), | |
| 142 ); | |
| 142 dest_buffer | 143 dest_buffer |
| 143 }; | 144 }; |
| 144 Ok(dest) | 145 Ok(dest) |
| 145 } | 146 } |
| 146 | 147 |
| 147 fn length(&self) -> usize { | 148 fn length(&self) -> usize { |
| 148 u32::from_be_bytes(self.total_length).saturating_sub(5) as usize | 149 u32::from_be_bytes(self.total_length).saturating_sub(5) as usize |
| 149 } | 150 } |
| 150 | 151 |
| 151 /// Clears this data and frees it. | 152 fn data_ptr(ptr: NonNull<Self>) -> *mut u8 { |
| 152 pub unsafe fn zero_contents(&mut self) { | 153 unsafe { |
| 153 let contents = slice::from_raw_parts_mut(self.data.as_mut_ptr(), self.length()); | 154 ptr.as_ptr() |
| 154 for v in contents { | 155 .cast::<u8>() |
| 155 *v = 0 | 156 .byte_offset(offset_of!(Self, data) as isize) |
| 156 } | 157 } |
| 157 self.data_type = 0; | 158 } |
| 158 self.total_length = [0; 4]; | 159 |
| 159 } | 160 unsafe fn data_slice<'a>(ptr: NonNull<Self>) -> &'a mut [u8] { |
| 160 } | 161 unsafe { slice::from_raw_parts_mut(Self::data_ptr(ptr), ptr.as_ref().length()) } |
| 161 | 162 } |
| 162 impl<'a> From<&'a CBinaryData> for (&'a [u8], u8) { | 163 |
| 163 fn from(value: &'a CBinaryData) -> Self { | 164 pub unsafe fn data<'a>(ptr: NonNull<Self>) -> (&'a [u8], u8) { |
| 164 ( | 165 unsafe { (Self::data_slice(ptr), ptr.as_ref().data_type) } |
| 165 unsafe { slice::from_raw_parts(value.data.as_ptr(), value.length()) }, | 166 } |
| 166 value.data_type, | 167 |
| 167 ) | 168 pub unsafe fn zero_contents(ptr: NonNull<Self>) { |
| 168 } | 169 for byte in Self::data_slice(ptr) { |
| 169 } | 170 ptr::write_volatile(byte as *mut u8, mem::zeroed()); |
| 170 | 171 } |
| 171 impl From<&'_ CBinaryData> for BinaryData { | 172 ptr::write_volatile(ptr.as_ptr(), mem::zeroed()); |
| 172 fn from(value: &'_ CBinaryData) -> Self { | 173 } |
| 173 // This is a dumb trick but I like it because it is simply the presence | 174 |
| 174 // of `.map(|z: (_, _)| z)` in the middle of this that gives | 175 #[allow(clippy::wrong_self_convention)] |
| 175 // type inference the hint it needs to make this work. | 176 pub unsafe fn as_binary_data(ptr: NonNull<Self>) -> BinaryData { |
| 176 let [ret] = [value].map(Into::into).map(|z: (_, _)| z).map(Into::into); | 177 let (data, data_type) = unsafe { (CBinaryData::data_slice(ptr), ptr.as_ref().data_type) }; |
| 177 ret | 178 (Vec::from(data), data_type).into() |
| 178 } | |
| 179 } | |
| 180 | |
| 181 impl From<Option<&'_ CBinaryData>> for BinaryData { | |
| 182 fn from(value: Option<&CBinaryData>) -> Self { | |
| 183 value.map(Into::into).unwrap_or_default() | |
| 184 } | 179 } |
| 185 } | 180 } |
| 186 | 181 |
| 187 #[cfg(test)] | 182 #[cfg(test)] |
| 188 mod tests { | 183 mod tests { |
| 191 ErrorCode, | 186 ErrorCode, |
| 192 }; | 187 }; |
| 193 #[test] | 188 #[test] |
| 194 fn test_strings() { | 189 fn test_strings() { |
| 195 let str = malloc_str("hello there").unwrap(); | 190 let str = malloc_str("hello there").unwrap(); |
| 191 let str = str.as_ptr(); | |
| 196 malloc_str("hell\0 there").unwrap_err(); | 192 malloc_str("hell\0 there").unwrap_err(); |
| 197 unsafe { | 193 unsafe { |
| 198 let copied = copy_pam_string(str).unwrap(); | 194 let copied = copy_pam_string(str).unwrap(); |
| 199 assert_eq!("hello there", copied); | 195 assert_eq!("hello there", copied); |
| 200 zero_c_string(str); | 196 zero_c_string(str); |
