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 }