Mercurial > crates > nonstick
comparison 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 |
comparison
equal
deleted
inserted
replaced
72:47eb242a4f88 | 73:ac6881304c78 |
---|---|
1 //! Types used when dealing with PAM conversations. | 1 //! Types used when dealing with PAM conversations. |
2 | 2 |
3 use crate::conv::BinaryData; | |
3 use crate::pam_ffi::memory; | 4 use crate::pam_ffi::memory; |
4 use crate::pam_ffi::memory::{CBinaryData, Immovable, NulError, TooBigError}; | 5 use crate::pam_ffi::memory::{CBinaryData, Immovable, NulError, TooBigError}; |
5 use std::ffi::{c_char, c_int, c_void, CStr}; | 6 use crate::Response; |
7 use std::ffi::{c_int, c_void, CStr}; | |
6 use std::ops::{Deref, DerefMut}; | 8 use std::ops::{Deref, DerefMut}; |
7 use std::result::Result as StdResult; | 9 use std::result::Result as StdResult; |
8 use std::str::Utf8Error; | 10 use std::str::Utf8Error; |
9 use std::{mem, ptr, slice}; | 11 use std::{iter, mem, ptr, slice}; |
10 | 12 |
11 #[repr(transparent)] | 13 #[repr(transparent)] |
12 #[derive(Debug)] | 14 #[derive(Debug)] |
13 pub struct RawTextResponse(RawResponse); | 15 pub struct RawTextResponse(RawResponse); |
14 | 16 |
15 impl RawTextResponse { | 17 impl RawTextResponse { |
16 /// Allocates a new text response on the C heap. | 18 /// Interprets the provided `RawResponse` as a text response. |
17 /// | 19 /// |
18 /// Both `self` and its internal pointer are located on the C heap. | 20 /// # Safety |
21 /// | |
22 /// It's up to you to provide a response that is a `RawTextResponse`. | |
23 pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { | |
24 // SAFETY: We're provided a valid reference. | |
25 &mut *(from as *mut RawResponse).cast::<Self>() | |
26 } | |
27 | |
28 /// Fills in the provided `RawResponse` with the given text. | |
29 /// | |
19 /// You are responsible for calling [`free`](Self::free_contents) | 30 /// You are responsible for calling [`free`](Self::free_contents) |
20 /// on the pointer you get back when you're done with it. | 31 /// on the pointer you get back when you're done with it. |
21 pub fn fill(dest: &mut RawResponse, text: impl AsRef<str>) -> StdResult<&mut Self, NulError> { | 32 pub fn fill(dest: &mut RawResponse, text: impl AsRef<str>) -> StdResult<&mut Self, NulError> { |
22 dest.data = memory::malloc_str(text)?.cast(); | 33 dest.data = memory::malloc_str(text)?.cast(); |
23 Ok(unsafe { &mut *(dest as *mut RawResponse as *mut Self) }) | 34 // SAFETY: We just filled this in so we know it's a text response. |
35 Ok(unsafe { Self::upcast(dest) }) | |
24 } | 36 } |
25 | 37 |
26 /// Gets the string stored in this response. | 38 /// Gets the string stored in this response. |
27 pub fn contents(&self) -> StdResult<&str, Utf8Error> { | 39 pub fn contents(&self) -> StdResult<&str, Utf8Error> { |
28 // SAFETY: This data is either passed from PAM (so we are forced to | 40 if self.0.data.is_null() { |
29 // trust it) or was created by us in TextResponseInner::alloc. | 41 Ok("") |
30 // In either case, it's going to be a valid null-terminated string. | 42 } else { |
31 unsafe { CStr::from_ptr(self.0.data as *const c_char) }.to_str() | 43 // SAFETY: This data is either passed from PAM (so we are forced to |
44 // trust it) or was created by us in TextResponseInner::alloc. | |
45 // In either case, it's going to be a valid null-terminated string. | |
46 unsafe { CStr::from_ptr(self.0.data.cast()) }.to_str() | |
47 } | |
32 } | 48 } |
33 | 49 |
34 /// Releases memory owned by this response. | 50 /// Releases memory owned by this response. |
35 /// | 51 pub fn free_contents(&mut self) { |
36 /// # Safety | 52 // SAFETY: We know we own this data. |
37 /// | 53 // After we're done, it will be null. |
38 /// You are responsible for no longer using this after calling free. | 54 unsafe { |
39 pub unsafe fn free_contents(&mut self) { | 55 memory::zero_c_string(self.0.data); |
40 let data = self.0.data; | 56 libc::free(self.0.data); |
41 memory::zero_c_string(data); | 57 self.0.data = ptr::null_mut() |
42 libc::free(data); | 58 } |
43 self.0.data = ptr::null_mut() | |
44 } | 59 } |
45 } | 60 } |
46 | 61 |
47 /// A [`RawResponse`] with [`CBinaryData`] in it. | 62 /// A [`RawResponse`] with [`CBinaryData`] in it. |
48 #[repr(transparent)] | 63 #[repr(transparent)] |
49 #[derive(Debug)] | 64 #[derive(Debug)] |
50 pub struct RawBinaryResponse(RawResponse); | 65 pub struct RawBinaryResponse(RawResponse); |
51 | 66 |
52 impl RawBinaryResponse { | 67 impl RawBinaryResponse { |
53 /// Allocates a new binary response on the C heap. | 68 /// Interprets the provided `RawResponse` as a binary response. |
69 /// | |
70 /// # Safety | |
71 /// | |
72 /// It's up to you to provide a response that is a `RawBinaryResponse`. | |
73 pub unsafe fn upcast(from: &mut RawResponse) -> &mut Self { | |
74 // SAFETY: We're provided a valid reference. | |
75 &mut *(from as *mut RawResponse).cast::<Self>() | |
76 } | |
77 | |
78 /// Fills in a `RawResponse` with the provided binary data. | |
54 /// | 79 /// |
55 /// The `data_type` is a tag you can use for whatever. | 80 /// The `data_type` is a tag you can use for whatever. |
56 /// It is passed through PAM unchanged. | 81 /// It is passed through PAM unchanged. |
57 /// | 82 /// |
58 /// The referenced data is copied to the C heap. We do not take ownership. | 83 /// The referenced data is copied to the C heap. We do not take ownership. |
61 pub fn fill<'a>( | 86 pub fn fill<'a>( |
62 dest: &'a mut RawResponse, | 87 dest: &'a mut RawResponse, |
63 data: &[u8], | 88 data: &[u8], |
64 data_type: u8, | 89 data_type: u8, |
65 ) -> StdResult<&'a mut Self, TooBigError> { | 90 ) -> StdResult<&'a mut Self, TooBigError> { |
66 dest.data = CBinaryData::alloc(data, data_type)? as *mut c_void; | 91 dest.data = CBinaryData::alloc(data, data_type)?.cast(); |
67 Ok(unsafe { | 92 // SAFETY: We just filled this in, so we know it's binary. |
68 (dest as *mut RawResponse) | 93 Ok(unsafe { Self::upcast(dest) }) |
69 .cast::<RawBinaryResponse>() | |
70 .as_mut() | |
71 .unwrap() | |
72 }) | |
73 } | 94 } |
74 | 95 |
75 /// Gets the binary data in this response. | 96 /// Gets the binary data in this response. |
76 pub fn contents(&self) -> &[u8] { | 97 pub fn data(&self) -> &[u8] { |
77 self.data().contents() | 98 self.contents().map(CBinaryData::contents).unwrap_or(&[]) |
78 } | 99 } |
79 | 100 |
80 /// Gets the `data_type` tag that was embedded with the message. | 101 /// Gets the `data_type` tag that was embedded with the message. |
81 pub fn data_type(&self) -> u8 { | 102 pub fn data_type(&self) -> u8 { |
82 self.data().data_type() | 103 self.contents().map(CBinaryData::data_type).unwrap_or(0) |
83 } | 104 } |
84 | 105 |
85 #[inline] | 106 fn contents(&self) -> Option<&CBinaryData> { |
86 fn data(&self) -> &CBinaryData { | |
87 // SAFETY: This was either something we got from PAM (in which case | 107 // SAFETY: This was either something we got from PAM (in which case |
88 // we trust it), or something that was created with | 108 // we trust it), or something that was created with |
89 // BinaryResponseInner::alloc. In both cases, it points to valid data. | 109 // BinaryResponseInner::alloc. In both cases, it points to valid data. |
90 unsafe { &*(self.0.data as *const CBinaryData) } | 110 unsafe { self.0.data.cast::<CBinaryData>().as_ref() } |
111 } | |
112 | |
113 pub fn to_owned(&self) -> BinaryData { | |
114 BinaryData::new(self.data().into(), self.data_type()) | |
91 } | 115 } |
92 | 116 |
93 /// Releases memory owned by this response. | 117 /// Releases memory owned by this response. |
94 /// | 118 pub fn free_contents(&mut self) { |
95 /// # Safety | 119 // SAFETY: We know that our data pointer is either valid or null. |
96 /// | 120 // Once we're done, it's null and the response is safe. |
97 /// You are responsible for not using this after calling free. | 121 unsafe { |
98 pub unsafe fn free_contents(&mut self) { | 122 let data_ref = self.0.data.cast::<CBinaryData>().as_mut(); |
99 let data_ref = (self.0.data as *mut CBinaryData).as_mut(); | 123 if let Some(d) = data_ref { |
100 if let Some(d) = data_ref { | 124 d.zero_contents() |
101 d.zero_contents() | 125 } |
102 } | 126 libc::free(self.0.data); |
103 libc::free(self.0.data); | 127 self.0.data = ptr::null_mut() |
104 self.0.data = ptr::null_mut() | 128 } |
105 } | 129 } |
106 } | 130 } |
107 | 131 |
108 /// Generic version of response data. | 132 /// Generic version of response data. |
109 /// | 133 /// |
122 _marker: Immovable, | 146 _marker: Immovable, |
123 } | 147 } |
124 | 148 |
125 /// A contiguous block of responses. | 149 /// A contiguous block of responses. |
126 #[derive(Debug)] | 150 #[derive(Debug)] |
127 #[repr(C)] | |
128 pub struct OwnedResponses { | 151 pub struct OwnedResponses { |
129 base: *mut RawResponse, | 152 base: *mut RawResponse, |
130 count: usize, | 153 count: usize, |
131 } | 154 } |
132 | 155 |
133 impl OwnedResponses { | 156 impl OwnedResponses { |
134 /// Allocates an owned list of responses on the C heap. | 157 /// Allocates an owned list of responses on the C heap. |
135 fn alloc(count: usize) -> Self { | 158 fn alloc(count: usize) -> Self { |
136 OwnedResponses { | 159 OwnedResponses { |
137 // SAFETY: We are doing allocation here. | 160 // SAFETY: We are doing allocation here. |
138 base: unsafe { libc::calloc(count, size_of::<RawResponse>()) } as *mut RawResponse, | 161 base: unsafe { libc::calloc(count, size_of::<RawResponse>()) }.cast(), |
139 count, | 162 count, |
140 } | 163 } |
141 } | 164 } |
142 | 165 |
166 pub fn build(value: &[Response]) -> StdResult<Self, FillError> { | |
167 let mut outputs = OwnedResponses::alloc(value.len()); | |
168 // If we fail in here after allocating OwnedResponses, | |
169 // we still free all memory, even though we don't zero it first. | |
170 // This is an acceptable level of risk. | |
171 for (input, output) in iter::zip(value.iter(), outputs.iter_mut()) { | |
172 match input { | |
173 Response::NoResponse => { | |
174 RawTextResponse::fill(output, "")?; | |
175 } | |
176 Response::Text(data) => { | |
177 RawTextResponse::fill(output, data)?; | |
178 } | |
179 Response::MaskedText(data) => { | |
180 RawTextResponse::fill(output, data.unsecure())?; | |
181 } | |
182 Response::Binary(data) => { | |
183 RawBinaryResponse::fill(output, data.data(), data.data_type())?; | |
184 } | |
185 } | |
186 } | |
187 Ok(outputs) | |
188 } | |
189 | |
190 /// Converts this into a `*RawResponse` for passing to PAM. | |
191 /// | |
192 /// The pointer "owns" its own data (i.e., this will not be dropped). | |
193 pub fn into_ptr(self) -> *mut RawResponse { | |
194 let ret = self.base; | |
195 mem::forget(self); | |
196 ret | |
197 } | |
198 | |
143 /// Takes ownership of a list of responses allocated on the C heap. | 199 /// Takes ownership of a list of responses allocated on the C heap. |
144 /// | 200 /// |
145 /// # Safety | 201 /// # Safety |
146 /// | 202 /// |
147 /// It's up to you to make sure you pass a valid pointer. | 203 /// It's up to you to make sure you pass a valid pointer. |
148 unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { | 204 pub unsafe fn from_c_heap(base: *mut RawResponse, count: usize) -> Self { |
149 OwnedResponses { base, count } | 205 OwnedResponses { base, count } |
150 } | 206 } |
151 } | 207 } |
152 | 208 |
153 impl From<OwnedResponses> for *mut RawResponse { | 209 #[derive(Debug, thiserror::Error)] |
154 /// Converts this into a pointer to `RawResponse`. | 210 #[error("error converting responses: {0}")] |
155 /// | 211 pub enum FillError { |
156 /// The backing data is no longer freed. | 212 NulError(#[from] NulError), |
157 fn from(value: OwnedResponses) -> Self { | 213 TooBigError(#[from] TooBigError), |
158 let ret = value.base; | |
159 mem::forget(value); | |
160 ret | |
161 } | |
162 } | 214 } |
163 | 215 |
164 impl Deref for OwnedResponses { | 216 impl Deref for OwnedResponses { |
165 type Target = [RawResponse]; | 217 type Target = [RawResponse]; |
166 fn deref(&self) -> &Self::Target { | 218 fn deref(&self) -> &Self::Target { |
167 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. | 219 // SAFETY: This is the memory we manage ourselves. |
168 unsafe { slice::from_raw_parts(self.base, self.count) } | 220 unsafe { slice::from_raw_parts(self.base, self.count) } |
169 } | 221 } |
170 } | 222 } |
171 | 223 |
172 impl DerefMut for OwnedResponses { | 224 impl DerefMut for OwnedResponses { |
173 fn deref_mut(&mut self) -> &mut Self::Target { | 225 fn deref_mut(&mut self) -> &mut Self::Target { |
174 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. | 226 // SAFETY: This is the memory we manage ourselves. |
175 unsafe { slice::from_raw_parts_mut(self.base, self.count) } | 227 unsafe { slice::from_raw_parts_mut(self.base, self.count) } |
176 } | 228 } |
177 } | 229 } |
178 | 230 |
179 impl Drop for OwnedResponses { | 231 impl Drop for OwnedResponses { |
181 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. | 233 // SAFETY: We allocated this ourselves, or it was provided to us by PAM. |
182 unsafe { | 234 unsafe { |
183 for resp in self.iter_mut() { | 235 for resp in self.iter_mut() { |
184 libc::free(resp.data) | 236 libc::free(resp.data) |
185 } | 237 } |
186 libc::free(self.base as *mut c_void) | 238 libc::free(self.base.cast()) |
187 } | 239 } |
188 } | 240 } |
189 } | 241 } |
190 | 242 |
191 #[cfg(test)] | 243 #[cfg(test)] |
192 mod tests { | 244 mod tests { |
193 | 245 use super::{BinaryData, OwnedResponses, RawBinaryResponse, RawTextResponse, Response}; |
194 use super::{OwnedResponses, RawBinaryResponse, RawTextResponse}; | 246 |
247 #[test] | |
248 fn test_round_trip() { | |
249 let responses = [ | |
250 Response::Binary(BinaryData::new(vec![1, 2, 3], 99)), | |
251 Response::Text("whats going on".to_owned()), | |
252 Response::MaskedText("well then".into()), | |
253 Response::NoResponse, | |
254 Response::Text("bogus".to_owned()), | |
255 ]; | |
256 let sent = OwnedResponses::build(&responses).unwrap(); | |
257 let heap_resps = sent.into_ptr(); | |
258 let mut received = unsafe { OwnedResponses::from_c_heap(heap_resps, 5) }; | |
259 | |
260 let assert_text = |want, raw| { | |
261 let up = unsafe { RawTextResponse::upcast(raw) }; | |
262 assert_eq!(want, up.contents().unwrap()); | |
263 up.free_contents(); | |
264 assert_eq!("", up.contents().unwrap()); | |
265 }; | |
266 let assert_bin = |want_data: &[u8], want_type, raw| { | |
267 let up = unsafe { RawBinaryResponse::upcast(raw) }; | |
268 assert_eq!(want_data, up.data()); | |
269 assert_eq!(want_type, up.data_type()); | |
270 up.free_contents(); | |
271 let empty: [u8; 0] = []; | |
272 assert_eq!(&empty, up.data()); | |
273 assert_eq!(0, up.data_type()); | |
274 }; | |
275 if let [zero, one, two, three, four] = &mut received[..] { | |
276 assert_bin(&[1, 2, 3], 99, zero); | |
277 assert_text("whats going on", one); | |
278 assert_text("well then", two); | |
279 assert_text("", three); | |
280 assert_text("bogus", four); | |
281 } else { | |
282 panic!("wrong size!") | |
283 } | |
284 } | |
195 | 285 |
196 #[test] | 286 #[test] |
197 fn test_text_response() { | 287 fn test_text_response() { |
198 let mut responses = OwnedResponses::alloc(2); | 288 let mut responses = OwnedResponses::alloc(2); |
199 let text = RawTextResponse::fill(&mut responses[0], "hello").unwrap(); | 289 let text = RawTextResponse::fill(&mut responses[0], "hello").unwrap(); |
200 let data = text.contents().expect("valid"); | 290 let data = text.contents().expect("valid"); |
201 assert_eq!("hello", data); | 291 assert_eq!("hello", data); |
202 unsafe { | 292 text.free_contents(); |
203 text.free_contents(); | 293 text.free_contents(); |
204 text.free_contents(); | |
205 } | |
206 RawTextResponse::fill(&mut responses[1], "hell\0").expect_err("should error; contains nul"); | 294 RawTextResponse::fill(&mut responses[1], "hell\0").expect_err("should error; contains nul"); |
207 } | 295 } |
208 | 296 |
209 #[test] | 297 #[test] |
210 fn test_binary_response() { | 298 fn test_binary_response() { |
211 let mut responses = OwnedResponses::alloc(1); | 299 let mut responses = OwnedResponses::alloc(1); |
212 let real_data = [1, 2, 3, 4, 5, 6, 7, 8]; | 300 let real_data = [1, 2, 3, 4, 5, 6, 7, 8]; |
213 let resp = RawBinaryResponse::fill(&mut responses[0], &real_data, 7) | 301 let resp = RawBinaryResponse::fill(&mut responses[0], &real_data, 7) |
214 .expect("alloc should succeed"); | 302 .expect("alloc should succeed"); |
215 let data = resp.contents(); | 303 let data = resp.data(); |
216 assert_eq!(&real_data, data); | 304 assert_eq!(&real_data, data); |
217 assert_eq!(7, resp.data_type()); | 305 assert_eq!(7, resp.data_type()); |
218 unsafe { | 306 resp.free_contents(); |
219 resp.free_contents(); | 307 resp.free_contents(); |
220 resp.free_contents(); | |
221 } | |
222 } | 308 } |
223 | 309 |
224 #[test] | 310 #[test] |
225 #[ignore] | 311 #[ignore] |
226 fn test_binary_response_too_big() { | 312 fn test_binary_response_too_big() { |