1 // Copyright 2020, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! This module implements the Android Protected Confirmation (APC) service as defined
16 //! in the android.security.apc AIDL spec.
17
18 use std::{
19 cmp::PartialEq,
20 collections::HashMap,
21 sync::{mpsc::Sender, Arc, Mutex},
22 };
23
24 use crate::error::anyhow_error_to_cstring;
25 use crate::ks_err;
26 use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd};
27 use android_security_apc::aidl::android::security::apc::{
28 IConfirmationCallback::IConfirmationCallback,
29 IProtectedConfirmation::{BnProtectedConfirmation, IProtectedConfirmation},
30 ResponseCode::ResponseCode,
31 };
32 use android_security_apc::binder::{
33 BinderFeatures, ExceptionCode, Interface, Result as BinderResult, SpIBinder,
34 Status as BinderStatus, Strong, ThreadState,
35 };
36 use anyhow::{Context, Result};
37 use keystore2_apc_compat::ApcHal;
38 use keystore2_selinux as selinux;
39 use std::time::{Duration, Instant};
40
41 /// This is the main APC error type, it wraps binder exceptions and the
42 /// APC ResponseCode.
43 #[derive(Debug, thiserror::Error, PartialEq, Eq)]
44 pub enum Error {
45 /// Wraps an Android Protected Confirmation (APC) response code as defined by the
46 /// android.security.apc AIDL interface specification.
47 #[error("Error::Rc({0:?})")]
48 Rc(ResponseCode),
49 /// Wraps a Binder exception code other than a service specific exception.
50 #[error("Binder exception code {0:?}, {1:?}")]
51 Binder(ExceptionCode, i32),
52 }
53
54 impl Error {
55 /// Short hand for `Error::Rc(ResponseCode::SYSTEM_ERROR)`
sys() -> Self56 pub fn sys() -> Self {
57 Error::Rc(ResponseCode::SYSTEM_ERROR)
58 }
59
60 /// Short hand for `Error::Rc(ResponseCode::OPERATION_PENDING)`
pending() -> Self61 pub fn pending() -> Self {
62 Error::Rc(ResponseCode::OPERATION_PENDING)
63 }
64
65 /// Short hand for `Error::Rc(ResponseCode::CANCELLED)`
cancelled() -> Self66 pub fn cancelled() -> Self {
67 Error::Rc(ResponseCode::CANCELLED)
68 }
69
70 /// Short hand for `Error::Rc(ResponseCode::ABORTED)`
aborted() -> Self71 pub fn aborted() -> Self {
72 Error::Rc(ResponseCode::ABORTED)
73 }
74
75 /// Short hand for `Error::Rc(ResponseCode::IGNORED)`
ignored() -> Self76 pub fn ignored() -> Self {
77 Error::Rc(ResponseCode::IGNORED)
78 }
79
80 /// Short hand for `Error::Rc(ResponseCode::UNIMPLEMENTED)`
unimplemented() -> Self81 pub fn unimplemented() -> Self {
82 Error::Rc(ResponseCode::UNIMPLEMENTED)
83 }
84 }
85
86 /// This function should be used by confirmation service calls to translate error conditions
87 /// into service specific exceptions.
88 ///
89 /// All error conditions get logged by this function.
90 ///
91 /// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`.
92 /// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`.
93 ///
94 /// All non `Error` error conditions get mapped onto ResponseCode::SYSTEM_ERROR`.
95 ///
96 /// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed
97 /// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it
98 /// typically returns Ok(value).
map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> where F: FnOnce(U) -> BinderResult<T>,99 pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T>
100 where
101 F: FnOnce(U) -> BinderResult<T>,
102 {
103 result.map_or_else(
104 |e| {
105 log::error!("{:#?}", e);
106 let root_cause = e.root_cause();
107 let rc = match root_cause.downcast_ref::<Error>() {
108 Some(Error::Rc(rcode)) => rcode.0,
109 Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0,
110 None => match root_cause.downcast_ref::<selinux::Error>() {
111 Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0,
112 _ => ResponseCode::SYSTEM_ERROR.0,
113 },
114 };
115 Err(BinderStatus::new_service_specific_error(
116 rc,
117 anyhow_error_to_cstring(&e).as_deref(),
118 ))
119 },
120 handle_ok,
121 )
122 }
123
124 /// Rate info records how many failed attempts a client has made to display a protected
125 /// confirmation prompt. Clients are penalized for attempts that get declined by the user
126 /// or attempts that get aborted by the client itself.
127 ///
128 /// After the third failed attempt the client has to cool down for 30 seconds before it
129 /// it can retry. After the sixth failed attempt, the time doubles with every failed attempt
130 /// until it goes into saturation at 24h.
131 ///
132 /// A successful user prompt resets the counter.
133 #[derive(Debug, Clone)]
134 struct RateInfo {
135 counter: u32,
136 timestamp: Instant,
137 }
138
139 impl RateInfo {
140 const ONE_DAY: Duration = Duration::from_secs(60u64 * 60u64 * 24u64);
141
get_remaining_back_off(&self) -> Option<Duration>142 fn get_remaining_back_off(&self) -> Option<Duration> {
143 let back_off = match self.counter {
144 // The first three attempts come without penalty.
145 0..=2 => return None,
146 // The next three attempts are are penalized with 30 seconds back off time.
147 3..=5 => Duration::from_secs(30),
148 // After that we double the back off time the with every additional attempt
149 // until we reach 1024m (~17h).
150 6..=16 => Duration::from_secs(60)
151 .checked_mul(1u32 << (self.counter - 6))
152 .unwrap_or(Self::ONE_DAY),
153 // After that we cap of at 24h between attempts.
154 _ => Self::ONE_DAY,
155 };
156 let elapsed = self.timestamp.elapsed();
157 // This does exactly what we want.
158 // `back_off - elapsed` is the remaining back off duration or None if elapsed is larger
159 // than back_off. Also, this operation cannot overflow as long as elapsed is less than
160 // back_off, which is all that we care about.
161 back_off.checked_sub(elapsed)
162 }
163 }
164
165 impl Default for RateInfo {
default() -> Self166 fn default() -> Self {
167 Self { counter: 0u32, timestamp: Instant::now() }
168 }
169 }
170
171 /// The APC session state represents the state of an APC session.
172 struct ApcSessionState {
173 /// A reference to the APC HAL backend.
174 hal: Arc<ApcHal>,
175 /// The client callback object.
176 cb: SpIBinder,
177 /// The uid of the owner of this APC session.
178 uid: u32,
179 /// The time when this session was started.
180 start: Instant,
181 /// This is set when the client calls abort.
182 /// This is used by the rate limiting logic to determine
183 /// if the client needs to be penalized for this attempt.
184 client_aborted: bool,
185 }
186
187 struct ApcState {
188 session: Option<ApcSessionState>,
189 rate_limiting: HashMap<u32, RateInfo>,
190 confirmation_token_sender: Sender<Vec<u8>>,
191 }
192
193 impl ApcState {
new(confirmation_token_sender: Sender<Vec<u8>>) -> Self194 fn new(confirmation_token_sender: Sender<Vec<u8>>) -> Self {
195 Self { session: None, rate_limiting: Default::default(), confirmation_token_sender }
196 }
197 }
198
199 /// Implementation of the APC service.
200 pub struct ApcManager {
201 state: Arc<Mutex<ApcState>>,
202 }
203
204 impl Interface for ApcManager {}
205
206 impl ApcManager {
207 /// Create a new instance of the Android Protected Confirmation service.
new_native_binder( confirmation_token_sender: Sender<Vec<u8>>, ) -> Result<Strong<dyn IProtectedConfirmation>>208 pub fn new_native_binder(
209 confirmation_token_sender: Sender<Vec<u8>>,
210 ) -> Result<Strong<dyn IProtectedConfirmation>> {
211 Ok(BnProtectedConfirmation::new_binder(
212 Self { state: Arc::new(Mutex::new(ApcState::new(confirmation_token_sender))) },
213 BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() },
214 ))
215 }
216
result( state: Arc<Mutex<ApcState>>, rc: u32, data_confirmed: Option<&[u8]>, confirmation_token: Option<&[u8]>, )217 fn result(
218 state: Arc<Mutex<ApcState>>,
219 rc: u32,
220 data_confirmed: Option<&[u8]>,
221 confirmation_token: Option<&[u8]>,
222 ) {
223 let mut state = state.lock().unwrap();
224 let (callback, uid, start, client_aborted) = match state.session.take() {
225 None => return, // Nothing to do
226 Some(ApcSessionState { cb: callback, uid, start, client_aborted, .. }) => {
227 (callback, uid, start, client_aborted)
228 }
229 };
230
231 let rc = compat_2_response_code(rc);
232
233 // Update rate limiting information.
234 match (rc, client_aborted, confirmation_token) {
235 // If the user confirmed the dialog.
236 (ResponseCode::OK, _, Some(confirmation_token)) => {
237 // Reset counter.
238 state.rate_limiting.remove(&uid);
239 // Send confirmation token to the enforcement module.
240 if let Err(e) = state.confirmation_token_sender.send(confirmation_token.to_vec()) {
241 log::error!("Got confirmation token, but receiver would not have it. {:?}", e);
242 }
243 }
244 // If cancelled by the user or if aborted by the client.
245 (ResponseCode::CANCELLED, _, _) | (ResponseCode::ABORTED, true, _) => {
246 // Penalize.
247 let mut rate_info = state.rate_limiting.entry(uid).or_default();
248 rate_info.counter += 1;
249 rate_info.timestamp = start;
250 }
251 (ResponseCode::OK, _, None) => {
252 log::error!(
253 "Confirmation prompt was successful but no confirmation token was returned."
254 );
255 }
256 // In any other case this try does not count at all.
257 _ => {}
258 }
259 drop(state);
260
261 if let Ok(listener) = callback.into_interface::<dyn IConfirmationCallback>() {
262 if let Err(e) = listener.onCompleted(rc, data_confirmed) {
263 log::error!("Reporting completion to client failed {:?}", e)
264 }
265 } else {
266 log::error!("SpIBinder is not a IConfirmationCallback.");
267 }
268 }
269
present_prompt( &self, listener: &binder::Strong<dyn IConfirmationCallback>, prompt_text: &str, extra_data: &[u8], locale: &str, ui_option_flags: i32, ) -> Result<()>270 fn present_prompt(
271 &self,
272 listener: &binder::Strong<dyn IConfirmationCallback>,
273 prompt_text: &str,
274 extra_data: &[u8],
275 locale: &str,
276 ui_option_flags: i32,
277 ) -> Result<()> {
278 let mut state = self.state.lock().unwrap();
279 if state.session.is_some() {
280 return Err(Error::pending()).context(ks_err!("APC Session pending."));
281 }
282
283 // Perform rate limiting.
284 let uid = ThreadState::get_calling_uid();
285 match state.rate_limiting.get(&uid) {
286 None => {}
287 Some(rate_info) => {
288 if let Some(back_off) = rate_info.get_remaining_back_off() {
289 return Err(Error::sys()).context(ks_err!(
290 "APC Cooling down. Remaining back-off: {}s",
291 back_off.as_secs()
292 ));
293 }
294 }
295 }
296
297 let hal = ApcHal::try_get_service();
298 let hal = match hal {
299 None => {
300 return Err(Error::unimplemented()).context(ks_err!("APC not supported."));
301 }
302 Some(h) => Arc::new(h),
303 };
304
305 let ui_opts = ui_opts_2_compat(ui_option_flags);
306
307 let state_clone = self.state.clone();
308 hal.prompt_user_confirmation(
309 prompt_text,
310 extra_data,
311 locale,
312 ui_opts,
313 move |rc, data_confirmed, confirmation_token| {
314 Self::result(state_clone, rc, data_confirmed, confirmation_token)
315 },
316 )
317 .map_err(|rc| Error::Rc(compat_2_response_code(rc)))
318 .context(ks_err!("APC Failed to present prompt."))?;
319 state.session = Some(ApcSessionState {
320 hal,
321 cb: listener.as_binder(),
322 uid,
323 start: Instant::now(),
324 client_aborted: false,
325 });
326 Ok(())
327 }
328
cancel_prompt(&self, listener: &binder::Strong<dyn IConfirmationCallback>) -> Result<()>329 fn cancel_prompt(&self, listener: &binder::Strong<dyn IConfirmationCallback>) -> Result<()> {
330 let mut state = self.state.lock().unwrap();
331 let hal = match &mut state.session {
332 None => {
333 return Err(Error::ignored())
334 .context(ks_err!("Attempt to cancel non existing session. Ignoring."));
335 }
336 Some(session) => {
337 if session.cb != listener.as_binder() {
338 return Err(Error::ignored()).context(ks_err!(
339 "Attempt to cancel session not belonging to caller. Ignoring."
340 ));
341 }
342 session.client_aborted = true;
343 session.hal.clone()
344 }
345 };
346 drop(state);
347 hal.abort();
348 Ok(())
349 }
350
is_supported() -> Result<bool>351 fn is_supported() -> Result<bool> {
352 Ok(ApcHal::try_get_service().is_some())
353 }
354 }
355
356 impl IProtectedConfirmation for ApcManager {
presentPrompt( &self, listener: &binder::Strong<dyn IConfirmationCallback>, prompt_text: &str, extra_data: &[u8], locale: &str, ui_option_flags: i32, ) -> BinderResult<()>357 fn presentPrompt(
358 &self,
359 listener: &binder::Strong<dyn IConfirmationCallback>,
360 prompt_text: &str,
361 extra_data: &[u8],
362 locale: &str,
363 ui_option_flags: i32,
364 ) -> BinderResult<()> {
365 // presentPrompt can take more time than other operations.
366 let _wp = wd::watch_millis("IProtectedConfirmation::presentPrompt", 3000);
367 map_or_log_err(
368 self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags),
369 Ok,
370 )
371 }
cancelPrompt( &self, listener: &binder::Strong<dyn IConfirmationCallback>, ) -> BinderResult<()>372 fn cancelPrompt(
373 &self,
374 listener: &binder::Strong<dyn IConfirmationCallback>,
375 ) -> BinderResult<()> {
376 let _wp = wd::watch_millis("IProtectedConfirmation::cancelPrompt", 500);
377 map_or_log_err(self.cancel_prompt(listener), Ok)
378 }
isSupported(&self) -> BinderResult<bool>379 fn isSupported(&self) -> BinderResult<bool> {
380 let _wp = wd::watch_millis("IProtectedConfirmation::isSupported", 500);
381 map_or_log_err(Self::is_supported(), Ok)
382 }
383 }
384