comparison src/libpam/question.rs @ 139:33b9622ed6d2

Remove redundant memory management in nonstick::libpam; fix UB. - Uses the libpam-sys-helpers BinaryPayload / OwnedBinaryPayload structs to handle memory management and parsing for Linux-PAM binary messages. - Gets rid of the (technically) undefined behavior in PtrPtrVec due to pointer provenance. - Don't check for malloc failing. It won't, even if it does. - Formatting/cleanups/etc.
author Paul Fisher <paul@pfish.zone>
date Thu, 03 Jul 2025 23:57:49 -0400
parents 80c07e5ab22f
children a508a69c068a
comparison
equal deleted inserted replaced
138:999bf07efbcb 139:33b9622ed6d2
1 //! Data and types dealing with PAM messages. 1 //! Data and types dealing with PAM messages.
2 2
3 #[cfg(feature = "linux-pam-ext")] 3 #[cfg(feature = "linux-pam-ext")]
4 use crate::conv::{BinaryQAndA, RadioQAndA}; 4 use crate::conv::{BinaryQAndA, RadioQAndA};
5 use libpam_sys_helpers::memory::{BinaryPayload, TooBigError};
5 use crate::conv::{ErrorMsg, Exchange, InfoMsg, MaskedQAndA, QAndA}; 6 use crate::conv::{ErrorMsg, Exchange, InfoMsg, MaskedQAndA, QAndA};
6 use crate::libpam::conversation::OwnedExchange; 7 use crate::libpam::conversation::OwnedExchange;
7 use crate::libpam::memory::{CBinaryData, CHeapBox, CHeapString}; 8 use crate::libpam::memory::{CHeapBox, CHeapPayload, CHeapString};
8 use crate::ErrorCode; 9 use crate::ErrorCode;
9 use crate::Result; 10 use crate::Result;
10 use num_enum::{IntoPrimitive, TryFromPrimitive}; 11 use num_enum::{IntoPrimitive, TryFromPrimitive};
11 use std::ffi::{c_int, c_void, CStr}; 12 use std::ffi::{c_int, c_void, CStr};
13 use std::ptr::NonNull;
12 14
13 mod style_const { 15 mod style_const {
14 pub use libpam_sys::*; 16 pub use libpam_sys::*;
15 #[cfg(not(feature = "link"))] 17 #[cfg(not(feature = "link"))]
16 #[cfg_pam_impl(not("LinuxPam"))] 18 #[cfg_pam_impl(not("LinuxPam"))]
44 } 46 }
45 47
46 /// A question sent by PAM or a module to an application. 48 /// A question sent by PAM or a module to an application.
47 /// 49 ///
48 /// PAM refers to this as a "message", but we call it a question 50 /// PAM refers to this as a "message", but we call it a question
49 /// to avoid confusion with [`Message`](crate::conv::Exchange). 51 /// to avoid confusion.
50 /// 52 ///
51 /// This question, and its internal data, is owned by its creator 53 /// This question, and its internal data, is owned by its creator
52 /// (either the module or PAM itself). 54 /// (either the module or PAM itself).
53 #[repr(C)] 55 #[repr(C)]
54 #[derive(Debug)] 56 #[derive(Debug)]
58 /// A description of the data requested. 60 /// A description of the data requested.
59 /// 61 ///
60 /// For most requests, this will be an owned [`CStr`], 62 /// For most requests, this will be an owned [`CStr`],
61 /// but for requests with style `PAM_BINARY_PROMPT`, 63 /// but for requests with style `PAM_BINARY_PROMPT`,
62 /// this will be `CBinaryData` (a Linux-PAM extension). 64 /// this will be `CBinaryData` (a Linux-PAM extension).
63 pub data: Option<CHeapBox<c_void>>, 65 pub data: Option<NonNull<c_void>>,
64 } 66 }
65 67
66 impl Question { 68 impl Question {
67 /// Gets this message's data pointer as a string. 69 /// Gets this message's data pointer as a string.
68 /// 70 ///
70 /// 72 ///
71 /// It's up to you to pass this only on types with a string value. 73 /// It's up to you to pass this only on types with a string value.
72 unsafe fn string_data(&self) -> Result<&str> { 74 unsafe fn string_data(&self) -> Result<&str> {
73 match self.data.as_ref() { 75 match self.data.as_ref() {
74 None => Ok(""), 76 None => Ok(""),
75 Some(data) => CStr::from_ptr(CHeapBox::as_ptr(data).cast().as_ptr()) 77 Some(data) => CStr::from_ptr(data.as_ptr().cast())
76 .to_str() 78 .to_str()
77 .map_err(|_| ErrorCode::ConversationError), 79 .map_err(|_| ErrorCode::ConversationError),
78 } 80 }
79 } 81 }
80 82
81 /// Gets this message's data pointer as borrowed binary data. 83 /// Gets this message's data pointer as borrowed binary data.
82 unsafe fn binary_data(&self) -> (&[u8], u8) { 84 unsafe fn binary_data(&self) -> (&[u8], u8) {
83 self.data 85 self.data
84 .as_ref() 86 .as_ref()
85 .map(|data| CBinaryData::data(CHeapBox::as_ptr(data).cast())) 87 .map(|data| BinaryPayload::contents(data.as_ptr().cast()))
86 .unwrap_or_default() 88 .unwrap_or_default()
87 } 89 }
88 } 90 }
89 91
90 impl TryFrom<&Exchange<'_>> for Question { 92 impl TryFrom<&Exchange<'_>> for Question {
102 Exchange::Error(p) => alloc(Style::ErrorMsg, p.question()), 104 Exchange::Error(p) => alloc(Style::ErrorMsg, p.question()),
103 Exchange::Info(p) => alloc(Style::TextInfo, p.question()), 105 Exchange::Info(p) => alloc(Style::TextInfo, p.question()),
104 #[cfg(feature = "linux-pam-ext")] 106 #[cfg(feature = "linux-pam-ext")]
105 Exchange::RadioPrompt(p) => alloc(Style::RadioType, p.question()), 107 Exchange::RadioPrompt(p) => alloc(Style::RadioType, p.question()),
106 #[cfg(feature = "linux-pam-ext")] 108 #[cfg(feature = "linux-pam-ext")]
107 Exchange::BinaryPrompt(p) => Ok((Style::BinaryPrompt, unsafe { 109 Exchange::BinaryPrompt(p) => {
108 CHeapBox::cast(CBinaryData::alloc(p.question())?) 110 let (data, typ) = p.question();
109 })), 111 let payload = CHeapPayload::new(data, typ)?.into_inner();
112 Ok((Style::BinaryPrompt, unsafe { CHeapBox::cast(payload) }))
113 },
110 #[cfg(not(feature = "linux-pam-ext"))] 114 #[cfg(not(feature = "linux-pam-ext"))]
111 Exchange::RadioPrompt(_) | Exchange::BinaryPrompt(_) => { 115 Exchange::RadioPrompt(_) | Exchange::BinaryPrompt(_) => {
112 Err(ErrorCode::ConversationError) 116 Err(ErrorCode::ConversationError)
113 } 117 }
114 }?; 118 }?;
115 Ok(Self { 119 Ok(Self {
116 style: style.into(), 120 style: style.into(),
117 data: Some(data), 121 data: Some(CHeapBox::into_ptr(data)),
118 }) 122 })
119 } 123 }
120 } 124 }
121 125
122 impl Drop for Question { 126 impl Drop for Question {
129 if let Ok(style) = Style::try_from(self.style) { 133 if let Ok(style) = Style::try_from(self.style) {
130 let _ = match style { 134 let _ = match style {
131 #[cfg(feature = "linux-pam-ext")] 135 #[cfg(feature = "linux-pam-ext")]
132 Style::BinaryPrompt => self 136 Style::BinaryPrompt => self
133 .data 137 .data
134 .as_ref() 138 .as_mut()
135 .map(|p| CBinaryData::zero_contents(CHeapBox::as_ptr(p).cast())), 139 .map(|p| BinaryPayload::zero(p.as_ptr().cast())),
136 #[cfg(feature = "linux-pam-ext")] 140 #[cfg(feature = "linux-pam-ext")]
137 Style::RadioType => self 141 Style::RadioType => self
138 .data 142 .data
139 .as_ref() 143 .as_mut()
140 .map(|p| CHeapString::zero(CHeapBox::as_ptr(p).cast())), 144 .map(|p| CHeapString::zero(p.cast())),
141 Style::TextInfo 145 Style::TextInfo
142 | Style::ErrorMsg 146 | Style::ErrorMsg
143 | Style::PromptEchoOff 147 | Style::PromptEchoOff
144 | Style::PromptEchoOn => self 148 | Style::PromptEchoOn => {
145 .data 149 self.data.as_mut().map(|p| CHeapString::zero(p.cast()))
146 .as_ref() 150 }
147 .map(|p| CHeapString::zero(CHeapBox::as_ptr(p).cast())),
148 }; 151 };
149 }; 152 };
153 let _ = self.data.map(|p| CHeapBox::from_ptr(p));
150 } 154 }
151 } 155 }
152 } 156 }
153 157
154 impl<'a> TryFrom<&'a Question> for OwnedExchange<'a> { 158 impl<'a> TryFrom<&'a Question> for OwnedExchange<'a> {
176 }; 180 };
177 Ok(prompt) 181 Ok(prompt)
178 } 182 }
179 } 183 }
180 184
185 #[cfg(feature = "linux-pam-ext")]
186 impl From<TooBigError> for ErrorCode {
187 fn from(_: TooBigError) -> Self {
188 ErrorCode::BufferError
189 }
190 }
191
181 #[cfg(test)] 192 #[cfg(test)]
182 mod tests { 193 mod tests {
183 use super::*; 194 use super::*;
184 195
185 macro_rules! assert_matches { 196 macro_rules! assert_matches {