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