Mercurial > crates > nonstick
diff src/pam_ffi/response.rs @ 73:ac6881304c78
Do conversations, along with way too much stuff.
This implements conversations, along with all the memory management
brouhaha that goes along with it. The conversation now lives directly
on the handle rather than being a thing you have to get from it
and then call manually. It Turns Out this makes things a lot easier!
I guess we reorganized things again. For the last time. For real.
I promise.
This all passes ASAN, so it seems Pretty Good!
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Thu, 05 Jun 2025 03:41:38 -0400 |
parents | 47eb242a4f88 |
children |
line wrap: on
line diff
--- a/src/pam_ffi/response.rs Wed Jun 04 03:53:36 2025 -0400 +++ b/src/pam_ffi/response.rs Thu Jun 05 03:41:38 2025 -0400 @@ -1,46 +1,61 @@ //! Types used when dealing with PAM conversations. +use crate::conv::BinaryData; use crate::pam_ffi::memory; use crate::pam_ffi::memory::{CBinaryData, Immovable, NulError, TooBigError}; -use std::ffi::{c_char, c_int, c_void, CStr}; +use crate::Response; +use std::ffi::{c_int, c_void, CStr}; use std::ops::{Deref, DerefMut}; use std::result::Result as StdResult; use std::str::Utf8Error; -use std::{mem, ptr, slice}; +use std::{iter, mem, ptr, slice}; #[repr(transparent)] #[derive(Debug)] pub struct RawTextResponse(RawResponse); impl RawTextResponse { - /// Allocates a new text response on the C heap. + /// Interprets the provided `RawResponse` as a text response. + /// + /// # Safety /// - /// Both `self` and its internal pointer are located on the C heap. + /// It's up to you to provide a response that is a `RawTextResponse`. + pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { + // SAFETY: We're provided a valid reference. + &mut *(from as *mut RawResponse).cast::<Self>() + } + + /// Fills in the provided `RawResponse` with the given text. + /// /// You are responsible for calling [`free`](Self::free_contents) /// on the pointer you get back when you're done with it. pub fn fill(dest: &mut RawResponse, text: impl AsRef<str>) -> StdResult<&mut Self, NulError> { dest.data = memory::malloc_str(text)?.cast(); - Ok(unsafe { &mut *(dest as *mut RawResponse as *mut Self) }) + // SAFETY: We just filled this in so we know it's a text response. + Ok(unsafe { Self::upcast(dest) }) } /// Gets the string stored in this response. pub fn contents(&self) -> StdResult<&str, Utf8Error> { - // SAFETY: This data is either passed from PAM (so we are forced to - // trust it) or was created by us in TextResponseInner::alloc. - // In either case, it's going to be a valid null-terminated string. - unsafe { CStr::from_ptr(self.0.data as *const c_char) }.to_str() + if self.0.data.is_null() { + Ok("") + } else { + // SAFETY: This data is either passed from PAM (so we are forced to + // trust it) or was created by us in TextResponseInner::alloc. + // In either case, it's going to be a valid null-terminated string. + unsafe { CStr::from_ptr(self.0.data.cast()) }.to_str() + } } /// Releases memory owned by this response. - /// - /// # Safety - /// - /// You are responsible for no longer using this after calling free. - pub unsafe fn free_contents(&mut self) { - let data = self.0.data; - memory::zero_c_string(data); - libc::free(data); - self.0.data = ptr::null_mut() + pub fn free_contents(&mut self) { + // SAFETY: We know we own this data. + // After we're done, it will be null. + unsafe { + memory::zero_c_string(self.0.data); + libc::free(self.0.data); + self.0.data = ptr::null_mut() + } } } @@ -50,7 +65,17 @@ pub struct RawBinaryResponse(RawResponse); impl RawBinaryResponse { - /// Allocates a new binary response on the C heap. + /// Interprets the provided `RawResponse` as a binary response. + /// + /// # Safety + /// + /// It's up to you to provide a response that is a `RawBinaryResponse`. + pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { + // SAFETY: We're provided a valid reference. + &mut *(from as *mut RawResponse).cast::<Self>() + } + + /// Fills in a `RawResponse` with the provided binary data. /// /// The `data_type` is a tag you can use for whatever. /// It is passed through PAM unchanged. @@ -63,45 +88,44 @@ data: &[u8], data_type: u8, ) -> StdResult<&'a mut Self, TooBigError> { - dest.data = CBinaryData::alloc(data, data_type)? as *mut c_void; - Ok(unsafe { - (dest as *mut RawResponse) - .cast::<RawBinaryResponse>() - .as_mut() - .unwrap() - }) + dest.data = CBinaryData::alloc(data, data_type)?.cast(); + // SAFETY: We just filled this in, so we know it's binary. + Ok(unsafe { Self::upcast(dest) }) } /// Gets the binary data in this response. - pub fn contents(&self) -> &[u8] { - self.data().contents() + pub fn data(&self) -> &[u8] { + self.contents().map(CBinaryData::contents).unwrap_or(&[]) } /// Gets the `data_type` tag that was embedded with the message. pub fn data_type(&self) -> u8 { - self.data().data_type() + self.contents().map(CBinaryData::data_type).unwrap_or(0) } - #[inline] - fn data(&self) -> &CBinaryData { + fn contents(&self) -> Option<&CBinaryData> { // SAFETY: This was either something we got from PAM (in which case // we trust it), or something that was created with // BinaryResponseInner::alloc. In both cases, it points to valid data. - unsafe { &*(self.0.data as *const CBinaryData) } + unsafe { self.0.data.cast::<CBinaryData>().as_ref() } + } + + pub fn to_owned(&self) -> BinaryData { + BinaryData::new(self.data().into(), self.data_type()) } /// Releases memory owned by this response. - /// - /// # Safety - /// - /// You are responsible for not using this after calling free. - pub unsafe fn free_contents(&mut self) { - let data_ref = (self.0.data as *mut CBinaryData).as_mut(); - if let Some(d) = data_ref { - d.zero_contents() + pub fn free_contents(&mut self) { + // SAFETY: We know that our data pointer is either valid or null. + // Once we're done, it's null and the response is safe. + unsafe { + let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); + if let Some(d) = data_ref { + d.zero_contents() + } + libc::free(self.0.data); + self.0.data = ptr::null_mut() } - libc::free(self.0.data); - self.0.data = ptr::null_mut() } } @@ -124,7 +148,6 @@ /// A contiguous block of responses. #[derive(Debug)] -#[repr(C)] pub struct OwnedResponses { base: *mut RawResponse, count: usize, @@ -135,43 +158,72 @@ fn alloc(count: usize) -> Self { OwnedResponses { // SAFETY: We are doing allocation here. - base: unsafe { libc::calloc(count, size_of::<RawResponse>()) } as *mut RawResponse, + base: unsafe { libc::calloc(count, size_of::<RawResponse>()) }.cast(), count, } } + pub fn build(value: &[Response]) -> StdResult<Self, FillError> { + let mut outputs = OwnedResponses::alloc(value.len()); + // If we fail in here after allocating OwnedResponses, + // we still free all memory, even though we don't zero it first. + // This is an acceptable level of risk. + for (input, output) in iter::zip(value.iter(), outputs.iter_mut()) { + match input { + Response::NoResponse => { + RawTextResponse::fill(output, "")?; + } + Response::Text(data) => { + RawTextResponse::fill(output, data)?; + } + Response::MaskedText(data) => { + RawTextResponse::fill(output, data.unsecure())?; + } + Response::Binary(data) => { + RawBinaryResponse::fill(output, data.data(), data.data_type())?; + } + } + } + Ok(outputs) + } + + /// Converts this into a `*RawResponse` for passing to PAM. + /// + /// The pointer "owns" its own data (i.e., this will not be dropped). + pub fn into_ptr(self) -> *mut RawResponse { + let ret = self.base; + mem::forget(self); + ret + } + /// Takes ownership of a list of responses allocated on the C heap. /// /// # Safety /// /// It's up to you to make sure you pass a valid pointer. - unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { + pub unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { OwnedResponses { base, count } } } -impl From<OwnedResponses> for *mut RawResponse { - /// Converts this into a pointer to `RawResponse`. - /// - /// The backing data is no longer freed. - fn from(value: OwnedResponses) -> Self { - let ret = value.base; - mem::forget(value); - ret - } +#[derive(Debug, thiserror::Error)] +#[error("error converting responses: {0}")] +pub enum FillError { + NulError(#[from] NulError), + TooBigError(#[from] TooBigError), } impl Deref for OwnedResponses { type Target = [RawResponse]; fn deref(&self) -> &Self::Target { - // SAFETY: We allocated this ourselves, or it was provided to us by PAM. + // SAFETY: This is the memory we manage ourselves. unsafe { slice::from_raw_parts(self.base, self.count) } } } impl DerefMut for OwnedResponses { fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: We allocated this ourselves, or it was provided to us by PAM. + // SAFETY: This is the memory we manage ourselves. unsafe { slice::from_raw_parts_mut(self.base, self.count) } } } @@ -183,15 +235,53 @@ for resp in self.iter_mut() { libc::free(resp.data) } - libc::free(self.base as *mut c_void) + libc::free(self.base.cast()) } } } #[cfg(test)] mod tests { + use super::{BinaryData, OwnedResponses, RawBinaryResponse, RawTextResponse, Response}; - use super::{OwnedResponses, RawBinaryResponse, RawTextResponse}; + #[test] + fn test_round_trip() { + let responses = [ + Response::Binary(BinaryData::new(vec![1, 2, 3], 99)), + Response::Text("whats going on".to_owned()), + Response::MaskedText("well then".into()), + Response::NoResponse, + Response::Text("bogus".to_owned()), + ]; + let sent = OwnedResponses::build(&responses).unwrap(); + let heap_resps = sent.into_ptr(); + let mut received = unsafe { OwnedResponses::from_c_heap(heap_resps, 5) }; + + let assert_text = |want, raw| { + let up = unsafe { RawTextResponse::upcast(raw) }; + assert_eq!(want, up.contents().unwrap()); + up.free_contents(); + assert_eq!("", up.contents().unwrap()); + }; + let assert_bin = |want_data: &[u8], want_type, raw| { + let up = unsafe { RawBinaryResponse::upcast(raw) }; + assert_eq!(want_data, up.data()); + assert_eq!(want_type, up.data_type()); + up.free_contents(); + let empty: [u8; 0] = []; + assert_eq!(&empty, up.data()); + assert_eq!(0, up.data_type()); + }; + if let [zero, one, two, three, four] = &mut received[..] { + assert_bin(&[1, 2, 3], 99, zero); + assert_text("whats going on", one); + assert_text("well then", two); + assert_text("", three); + assert_text("bogus", four); + } else { + panic!("wrong size!") + } + } #[test] fn test_text_response() { @@ -199,10 +289,8 @@ let text = RawTextResponse::fill(&mut responses[0], "hello").unwrap(); let data = text.contents().expect("valid"); assert_eq!("hello", data); - unsafe { - text.free_contents(); - text.free_contents(); - } + text.free_contents(); + text.free_contents(); RawTextResponse::fill(&mut responses[1], "hell\0").expect_err("should error; contains nul"); } @@ -212,13 +300,11 @@ let real_data = [1, 2, 3, 4, 5, 6, 7, 8]; let resp = RawBinaryResponse::fill(&mut responses[0], &real_data, 7) .expect("alloc should succeed"); - let data = resp.contents(); + let data = resp.data(); assert_eq!(&real_data, data); assert_eq!(7, resp.data_type()); - unsafe { - resp.free_contents(); - resp.free_contents(); - } + resp.free_contents(); + resp.free_contents(); } #[test]