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