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 } |