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