Mercurial > crates > nonstick
comparison src/libpam/memory.rs @ 143:ebb71a412b58
Turn everything into OsString and Just Walk Out! for strings with nul.
To reduce the hazard surface of the API, this replaces most uses of &str
with &OsStr (and likewise with String/OsString).
Also, I've decided that instead of dealing with callers putting `\0`
in their parameters, I'm going to follow the example of std::env and
Just Walk Out! (i.e., panic!()).
This makes things a lot less annoying for both me and (hopefully) users.
| author | Paul Fisher <paul@pfish.zone> |
|---|---|
| date | Sat, 05 Jul 2025 22:12:46 -0400 |
| parents | a508a69c068a |
| children | 4b3a5095f68c |
comparison
equal
deleted
inserted
replaced
| 142:5c1e315c18ff | 143:ebb71a412b58 |
|---|---|
| 1 //! Things for dealing with memory. | 1 //! Things for dealing with memory. |
| 2 | 2 |
| 3 use crate::ErrorCode; | |
| 4 use crate::Result; | |
| 5 use libpam_sys_helpers::memory::{Buffer, OwnedBinaryPayload}; | 3 use libpam_sys_helpers::memory::{Buffer, OwnedBinaryPayload}; |
| 6 use std::ffi::{c_char, CStr, CString}; | 4 use std::ffi::{c_char, CStr, CString, OsStr, OsString}; |
| 7 use std::marker::{PhantomData, PhantomPinned}; | 5 use std::marker::{PhantomData, PhantomPinned}; |
| 8 use std::mem::ManuallyDrop; | 6 use std::mem::ManuallyDrop; |
| 9 use std::ops::{Deref, DerefMut}; | 7 use std::ops::{Deref, DerefMut}; |
| 8 use std::os::unix::ffi::{OsStrExt, OsStringExt}; | |
| 10 use std::ptr::NonNull; | 9 use std::ptr::NonNull; |
| 11 use std::{mem, ptr, slice}; | 10 use std::{mem, ptr, slice}; |
| 12 | 11 |
| 13 /// Allocates `count` elements to hold `T`. | 12 /// Allocates `count` elements to hold `T`. |
| 14 #[inline] | 13 #[inline] |
| 31 #[repr(C)] | 30 #[repr(C)] |
| 32 #[derive(Debug, Default)] | 31 #[derive(Debug, Default)] |
| 33 pub struct Immovable(pub PhantomData<(*mut u8, PhantomPinned)>); | 32 pub struct Immovable(pub PhantomData<(*mut u8, PhantomPinned)>); |
| 34 | 33 |
| 35 /// Safely converts a `&str` option to a `CString` option. | 34 /// Safely converts a `&str` option to a `CString` option. |
| 36 pub fn option_cstr(prompt: Option<&str>) -> Result<Option<CString>> { | 35 pub fn option_cstr(prompt: Option<&[u8]>) -> Option<CString> { |
| 37 prompt | 36 prompt.map(|p| CString::new(p).expect("nul is not allowed")) |
| 38 .map(CString::new) | 37 } |
| 39 .transpose() | 38 |
| 40 .map_err(|_| ErrorCode::ConversationError) | 39 pub fn option_cstr_os(prompt: Option<&OsStr>) -> Option<CString> { |
| 40 option_cstr(prompt.map(OsStr::as_bytes)) | |
| 41 } | 41 } |
| 42 | 42 |
| 43 /// Gets the pointer to the given CString, or a null pointer if absent. | 43 /// Gets the pointer to the given CString, or a null pointer if absent. |
| 44 pub fn prompt_ptr(prompt: Option<&CString>) -> *const c_char { | 44 pub fn prompt_ptr(prompt: Option<&CStr>) -> *const c_char { |
| 45 match prompt { | 45 match prompt { |
| 46 Some(c_str) => c_str.as_ptr(), | 46 Some(c_str) => c_str.as_ptr(), |
| 47 None => ptr::null(), | 47 None => ptr::null(), |
| 48 } | 48 } |
| 49 } | 49 } |
| 166 #[repr(transparent)] | 166 #[repr(transparent)] |
| 167 pub struct CHeapString(CHeapBox<c_char>); | 167 pub struct CHeapString(CHeapBox<c_char>); |
| 168 | 168 |
| 169 impl CHeapString { | 169 impl CHeapString { |
| 170 /// Creates a new C heap string with the given contents. | 170 /// Creates a new C heap string with the given contents. |
| 171 pub fn new(text: &str) -> Result<Self> { | 171 pub fn new(text: impl AsRef<[u8]>) -> Self { |
| 172 let data = text.as_bytes(); | 172 let data = text.as_ref(); |
| 173 if data.contains(&0) { | 173 if data.contains(&0) { |
| 174 return Err(ErrorCode::ConversationError); | 174 panic!("you're not allowed to create a cstring with a nul inside!"); |
| 175 } | 175 } |
| 176 // +1 for the null terminator | 176 // +1 for the null terminator |
| 177 let data_alloc: NonNull<c_char> = calloc(data.len() + 1); | 177 let data_alloc: NonNull<c_char> = calloc(data.len() + 1); |
| 178 // SAFETY: we just allocated this and we have enough room. | 178 // SAFETY: we just allocated this and we have enough room. |
| 179 unsafe { | 179 unsafe { |
| 180 let dest = slice::from_raw_parts_mut(data_alloc.as_ptr().cast(), data.len()); | 180 let dest = slice::from_raw_parts_mut(data_alloc.as_ptr().cast(), data.len()); |
| 181 dest.copy_from_slice(data); | 181 dest.copy_from_slice(data); |
| 182 Ok(Self(CHeapBox::from_ptr(data_alloc))) | 182 Self(CHeapBox::from_ptr(data_alloc)) |
| 183 } | 183 } |
| 184 } | 184 } |
| 185 | 185 |
| 186 /// Converts this C heap string into a raw pointer. | 186 /// Converts this C heap string into a raw pointer. |
| 187 /// | 187 /// |
| 245 /// <code>pam_get_<var>whatever</var></code> function. | 245 /// <code>pam_get_<var>whatever</var></code> function. |
| 246 /// | 246 /// |
| 247 /// # Safety | 247 /// # Safety |
| 248 /// | 248 /// |
| 249 /// It's on you to provide a valid string. | 249 /// It's on you to provide a valid string. |
| 250 pub unsafe fn copy_pam_string(result_ptr: *const c_char) -> Result<Option<String>> { | 250 pub unsafe fn copy_pam_string(result_ptr: *const c_char) -> Option<OsString> { |
| 251 let borrowed = match NonNull::new(result_ptr.cast_mut()) { | 251 NonNull::new(result_ptr.cast_mut()) |
| 252 Some(data) => Some( | 252 .map(NonNull::as_ptr) |
| 253 CStr::from_ptr(data.as_ptr()) | 253 .map(|p| CStr::from_ptr(p)) |
| 254 .to_str() | 254 .map(CStr::to_bytes) |
| 255 .map_err(|_| ErrorCode::ConversationError)?, | 255 .map(Vec::from) |
| 256 ), | 256 .map(OsString::from_vec) |
| 257 None => return Ok(None), | |
| 258 }; | |
| 259 Ok(borrowed.map(String::from)) | |
| 260 } | 257 } |
| 261 | 258 |
| 262 #[cfg(test)] | 259 #[cfg(test)] |
| 263 mod tests { | 260 mod tests { |
| 264 use super::*; | 261 use super::*; |
| 283 *dropbox = Dropper(&drop_count); | 280 *dropbox = Dropper(&drop_count); |
| 284 assert_eq!(2, drop_count.get()); | 281 assert_eq!(2, drop_count.get()); |
| 285 drop(dropbox); | 282 drop(dropbox); |
| 286 assert_eq!(3, drop_count.get()); | 283 assert_eq!(3, drop_count.get()); |
| 287 } | 284 } |
| 285 | |
| 288 #[test] | 286 #[test] |
| 289 fn test_strings() { | 287 fn test_strings() { |
| 290 let str = CHeapString::new("hello there").unwrap(); | 288 let str = CHeapString::new("hello there"); |
| 291 let str_ptr = str.into_ptr().as_ptr(); | 289 let str_ptr = str.into_ptr().as_ptr(); |
| 292 CHeapString::new("hell\0 there").unwrap_err(); | |
| 293 unsafe { | 290 unsafe { |
| 294 let copied = copy_pam_string(str_ptr).unwrap(); | 291 let copied = copy_pam_string(str_ptr).unwrap(); |
| 295 assert_eq!("hello there", copied.unwrap()); | 292 assert_eq!("hello there", copied); |
| 296 CHeapString::zero(NonNull::new(str_ptr).unwrap()); | 293 CHeapString::zero(NonNull::new(str_ptr).unwrap()); |
| 297 let idx_three = str_ptr.add(3).as_mut().unwrap(); | 294 let idx_three = str_ptr.add(3).as_mut().unwrap(); |
| 298 *idx_three = 0x80u8 as i8; | 295 *idx_three = 0x80u8 as i8; |
| 299 let zeroed = copy_pam_string(str_ptr).unwrap().unwrap(); | 296 let zeroed = copy_pam_string(str_ptr).unwrap(); |
| 300 assert!(zeroed.is_empty()); | 297 assert!(zeroed.is_empty()); |
| 301 let _ = CHeapString::from_ptr(str_ptr); | 298 let _ = CHeapString::from_ptr(str_ptr); |
| 302 } | 299 } |
| 303 } | 300 } |
| 304 | 301 |
| 305 #[test] | 302 #[test] |
| 303 #[should_panic] | |
| 304 fn test_nul_string() { | |
| 305 CHeapString::new("hell\0 there"); | |
| 306 } | |
| 307 | |
| 308 #[test] | |
| 306 fn test_option_str() { | 309 fn test_option_str() { |
| 307 let good = option_cstr(Some("whatever")).unwrap(); | 310 let good = option_cstr(Some("whatever".as_ref())); |
| 308 assert_eq!("whatever", good.unwrap().to_str().unwrap()); | 311 assert_eq!("whatever", good.unwrap().to_str().unwrap()); |
| 309 let no_str = option_cstr(None).unwrap(); | 312 let no_str = option_cstr(None); |
| 310 assert!(no_str.is_none()); | 313 assert!(no_str.is_none()); |
| 311 let bad_str = option_cstr(Some("what\0ever")).unwrap_err(); | 314 } |
| 312 assert_eq!(ErrorCode::ConversationError, bad_str); | 315 #[test] |
| 316 #[should_panic] | |
| 317 fn test_nul_cstr() { | |
| 318 option_cstr(Some("what\0ever".as_ref())); | |
| 313 } | 319 } |
| 314 | 320 |
| 315 #[test] | 321 #[test] |
| 316 fn test_prompt() { | 322 fn test_prompt() { |
| 317 let prompt_cstr = CString::new("good").ok(); | 323 let prompt_cstr = CString::new("good").ok(); |
| 318 let prompt = prompt_ptr(prompt_cstr.as_ref()); | 324 let prompt = prompt_ptr(prompt_cstr.as_deref()); |
| 319 assert!(!prompt.is_null()); | 325 assert!(!prompt.is_null()); |
| 320 let no_prompt = prompt_ptr(None); | 326 let no_prompt = prompt_ptr(None); |
| 321 assert!(no_prompt.is_null()); | 327 assert!(no_prompt.is_null()); |
| 322 } | 328 } |
| 323 } | 329 } |
